QA@IT

twitter oauthで初めてログインした後のコールバックでredirect_to :back できない。

3128 PV

http://benkyoplayer.com のようなサイトで全てのページに"Sign in with Twitter"のようなリンクを張っています。

認証にはomniauth-twitterを使っているので実際にはこういうリンクになっています。

    <%= link_to "Sign in with Twitter", "/auth/twitter" %>

でツイッターの認証後のコールバックは以下のような感じです。

    class SessionsController < ApplicationController
      def create
         # do actual sign in.
        redirect_to :back, notice: "Signed in!"
      end

    end

「redirect_to :back」にすることによって、ユーザーがもともといたページに戻るようにしています。これはユーザーが以前にもbenkyoplayer.comにSign Inしたことがある場合(=Sign in Twitterリンクを押すとすぐにサインインされる)は有効なのですが、初めての認証の際にツイッターのページでユーザーネームとパスワードを要求された後にリダイレクトされると以下のようなエラーが出てしまいます。

    ActionController::RedirectBackError No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].

どうやらツイッターのページを経て戻ってくる場合はHTTP_REFERERが指定されていないようです。

これの応急処置として http://stackoverflow.com/questions/771656/correctly-doing-redirect-to-back-in-ruby-on-rails-when-referrer-is-not-availabl を参考に以下のようにしています。

    class SessionsController < ApplicationController
      def create
        begin
          # do actual sign in.
          redirect_to :back, notice: "Signed in!"
        rescue ActionController::RedirectBackError
          redirect_to root_path, notice: notice
        end
      end

    end

これだとエラーは出ませんが、初めて私のサイトを利用するユーザーが特定のページにいたにも関わらずroot_pathにリダイレクトされてしまうのはユーザーフロー上あまり好ましいとはいえないと思います。

その他StackOverFlowの解決法だと、/auth/twitterの前にget_redirectみたいなアクションをつくって以下のようにリダイレクトするのも考えられます。

    class AtuthController < ApplicationController
      def get_redirect
          session[:return_to] = request.request_uri
          redirect_to '/auth/twitter'
      end

      def create
           redirect_to(session[:return_to] || default)
           session[:return_to] = nil
      end
    end

ただ解決方法を自作するよりもっと良い方法がすでにあると思うので、もしもっと良い方法をご存知であればお知らせください。

回答

追記

あ、なるほど、勘違いしてました。そういうことであれば、

class ApplicationController < ActionController::Base
  def link_with_store_location(*args)
    session[:return_to] = request.fullpath
    view_context.link_to(*args)
  end
  helper_method :link_with_store_location
end

としておいて、レイアウト側で

= link_with_store_location 'Login with twitter', '/auth/twitter'

でどうでしょう?

旧回答

認証を必要とするページにbefore_filterを置いてると思いますが、ここで仮にそれをauthenticateというメソッドだとすると、その中でreturn_toをセットしてあげるのが定番の方法だと思いますがいかがでしょうか。

class ApplicationController < ActionController::Base
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  helper_method :current_user

  def authenticate
    unless current_user
      session[:return_to] = request.fullpath if request.get?
      redirect_to '/auth/twitter'
    end
  end
end
class AuthsController < ApplicationController
  def callback
    user = from_omniauth(request.env['omniauth.auth'])
    session[:user_id] = user.id
    redirect_to session.delete(:return_to) || root_path, notice: 'Logged in!'
  end
end
編集 履歴 (2)
  • 確かにこの方法のほうがより一般化されていますね。ありがとうございます。 -

お返事ありがとうございます。

ページごとに認証をする場合はそれでも良いのですが、認証を利用しないページ(ほとんどそうです)の時でもアクセスできるページで「コメントするにはサインインして下さい」とかの表示があるのでそういう場合には当てはまらないかと思います。

やはり A) omni_authの/auth/twitter pathを乗っ取る方法か、 B) /auth/twitter にリダイレクトするactionを作って通常のサインインリンクはそれを使うかなと思っています。

A)の方が変更するコード量は減りますが、omni_auth自身が上書きするAPIを用意していないと(omini_authのgithubページを探してみたのですが見つかりませんでした)、将来の挙動の変更でこちらのアプリが崩れる可能性があります。B)のほうが簡単で分かりやすいのですがバグもおきやすいのでので他に良い方法があればと思っています。

編集 履歴 (0)
  • なるほど、というわけで追記しました。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。