0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails】 devise_token_authのUnsafeRedirectError

Posted at

概要

ポートフォリオのRailsをAPIモードで作成した際、認証機能をdevise_token_authで実装した際のエラー対応です。

症状

devise_token_authでメール認証を実装する場合、作成したモデルに:confirmableを追加すると思います。
このメール認証には、認証成功時のリダイレクト先を指定する必要があるのですが、本番環境で以下のエラーが発生しました。

ActionController::Redirecting::UnsafeRedirectError
(Unsafe redirect to "https://省略...",
pass allow_other_host: true to redirect anyway.)

どうやらRails7から追加された Open Redirect protection により、外部URLへのリダイレクトが弾かれるようです。
私は、Rails APIとフロント側をそれぞれ別々のホストへデプロイしていたために発生した問題となります。
エラー文中でもpass allow_other_host: true to redirect anywayと、外部ホストを許可しろと言われています。

対応策

情報があまり無かったのですが、以下のissueを参考にしました。

どうやら、 confirmations_controller.rbshowアクションをオーバーライドして、 allow_other_host: true オプションを付与すればいいようです。
元のコードは、devise_token_authのGtHubから直接confirmations_controller.rbのコードを引っ張ってきました。

api/v1/auth/confirmations_controller.rb
# ActionController::Redirecting::UnsafeRedirectError: Unsafe redirect toのエラー対応
class Api::V1::Auth::ConfirmationsController < DeviseTokenAuth::ConfirmationsController
  def show
    @resource = resource_class.confirm_by_token(resource_params[:confirmation_token])

    if @resource.errors.empty?
      yield @resource if block_given?

      redirect_header_options = { account_confirmation_success: true }

      if signed_in?(resource_name)
        token = signed_in_resource.create_token
        signed_in_resource.save!

        redirect_headers = build_redirect_headers(token.token,
                                                  token.client,
                                                  redirect_header_options)

        redirect_to_link = signed_in_resource.build_auth_url(redirect_url, redirect_headers)
      else
        redirect_to_link = DeviseTokenAuth::Url.generate(redirect_url, redirect_header_options)
      end

      # allow_other_host: trueを追加することで、外部URLへのリダイレクトを許容する
      redirect_to(redirect_to_link, allow_other_host: true)
    elsif redirect_url
      redirect_to DeviseTokenAuth::Url.generate(redirect_url, account_confirmation_success: false), allow_other_host: true
    else
      raise ActionController::RoutingError, 'Not Found'
    end
  end
end

ルーティングも修正します。

config/routes.rb
Rails.application.routes.draw do
  namespace "api" do
    namespace "v1" do
      # devise_token_authのコントローラをオーバーライド
      mount_devise_token_auth_for "User", at: "auth", controllers: {
        confirmations: "api/v1/auth/confirmations",
      }
    end
  end
end

これで、devise_token_authのメール認証における外部URLへのリダイレクトが許可されるようになりました。

注意点として、外部URLへのリダイレクトを許容するためセキュリティリスクは高まる可能性があります。今回の実装には含めていませんが、リダイレクトの直前で、チェック処理を入れた方がいいかもしれません。

パスワード再設定時の同様のエラー

devise_token_authでパスワードを再設定する場合も、メールからパスワード設定用ページへリダイレクトする必要があるので、そこでも UnsafeRedirectError が発生します。

このエラーはpasswords_controller.rbeditアクションをオーバーライドする必要があります。

api/v1/auth/passwords_controller.rb
class Api::V1::Auth::PasswordsController < DeviseTokenAuth::PasswordsController
  def edit
    # if a user is not found, return nil
    @resource = resource_class.with_reset_password_token(resource_params[:reset_password_token])

    if @resource && @resource.reset_password_period_valid?
      token = @resource.create_token unless require_client_password_reset_token?

      # ensure that user is confirmed
      @resource.skip_confirmation! if confirmable_enabled? && !@resource.confirmed_at
      # allow user to change password once without current_password
      @resource.allow_password_change = true if recoverable_enabled?

      @resource.save!

      yield @resource if block_given?

      # allow_other_host: trueを追加することで、外部URLへのリダイレクトを許容する
      if require_client_password_reset_token?
        redirect_to DeviseTokenAuth::Url.generate(@redirect_url, reset_password_token: resource_params[:reset_password_token]),
        allow_other_host: true
      else
        if DeviseTokenAuth.cookie_enabled
          set_token_in_cookie(@resource, token)
        end

        redirect_header_options = { reset_password: true }
        redirect_headers = build_redirect_headers(token.token,
                                                  token.client,
                                                  redirect_header_options)
        redirect_to(@resource.build_auth_url(@redirect_url,
                                             redirect_headers),
                                             # allow_other_host: trueを追加することで、外部URLへのリダイレクトを許容する
                                             allow_other_host: true)
      end
    else
      render_edit_error
    end
  end
end

ルーティングに追加します。

config/routes.rb
Rails.application.routes.draw do
  namespace "api" do
    namespace "v1" do
      # devise_token_authのコントローラをオーバーライド
      mount_devise_token_auth_for "User", at: "auth", controllers: {
        confirmations: "api/v1/auth/confirmations",
        passwords: "api/v1/auth/passwords",
      }
    end
  end
end

これで、パスワード再設定も問題無くリダイレクトされるようになりました。

以上です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?