概要
ポートフォリオの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.rb
のshow
アクションをオーバーライドして、 allow_other_host: true
オプションを付与すればいいようです。
元のコードは、devise_token_authのGtHubから直接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
ルーティングも修正します。
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.rb
のedit
アクションをオーバーライドする必要があります。
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
ルーティングに追加します。
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
これで、パスワード再設定も問題無くリダイレクトされるようになりました。
以上です。