NuxtとRailsアプリ(Devise Token Auth)の結合テストをする をしてみて気付きましたが、「メールアドレス変更時に送信されるメールの確認URL」と「アカウントロック時に送信されるメールの解除URL」がフロントURLにならなかったので対応しました。
デフォルトURLを設定している場合は、それが使われます。(確認URLのみ)
そもそも
フロントのURLをバックエンドが知っている必要はないので、APIで渡したものを使いたい。
(但し、ドメインはホワイトリストで縛ってセキュアにする)
登録情報変更APIとログインAPIでURLを渡していない。
渡せるか確認しましたが、受け取るようになっていなかった。
ソース確認
devise_token_auth-1.2.0/app/models/devise_token_auth/concerns/user.rb
    # override devise method to include additional info as opts hash
    def send_confirmation_instructions(opts = {})
      generate_confirmation_token! unless @raw_confirmation_token
      # fall back to "default" config name
      opts[:client_config] ||= 'default'
      opts[:to] = unconfirmed_email if pending_reconfirmation?
      opts[:redirect_url] ||= DeviseTokenAuth.default_confirm_success_url
      send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
    end
RegistrationsControllerのcreateとConfirmationsControllerのcreateから、このメソッドが呼ばれているが、RegistrationsControllerのupdateからは直接呼ばれていない。
    # override devise method to include additional info as opts hash
    def send_unlock_instructions(opts = {})
      raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
      self.unlock_token = enc
      save(validate: false)
      # fall back to "default" config name
      opts[:client_config] ||= 'default'
      send_devise_notification(:unlock_instructions, raw, opts)
      raw
    end
UnlocksControllerのcreateからこのメソッドが呼ばれているが、SessionsControllerのcreateからは直接呼ばれていない。
コメント通りdeviseをoverrideしているが、RegistrationsControllerのupdateやSessionsControllerのcreateは、deviseの処理から呼ばれる為、optsが渡されてないし、そっちをoverrideしないと渡せない。
フロント修正
まずは、それぞれのAPIにredirect_urlを追加。
作成しているNuxtアプリに入れます。
components/users/edit/InfoEdit.vue
      await this.$axios.put(this.$config.apiBaseURL + this.$config.userUpdateUrl, {
        name: this.name,
        email: this.email,
        password: this.password,
        password_confirmation: this.password_confirmation,
        current_password: this.current_password,
+        confirm_redirect_url: this.$config.frontBaseURL + this.$config.confirmationSuccessUrl
      })
pages/users/sign_in.vue
      await this.$auth.loginWith('local', {
        data: {
          email: this.email,
          password: this.password,
+          unlock_redirect_url: this.$config.frontBaseURL + this.$config.unlockRedirectUrl
        }
      })
修正方針検討
overrideを増やしたくない。<- Gemに任せている範囲が狭くなるので、保守コストが上がる。
グローバル変数は使いたくない。<- スコープが広過ぎるので、危うい。
Devise::Mailer継承して(既にしていますが)、メールテンプレに渡すのが良さそう。
ただ、paramsやrequestは、ここでは参照できない。
ベストではないですが、controllerから渡せる値はrecord(Users)ぐらいなので、attr_accessorを使う事にしました。
作成しているRailsアプリに入れます。
app/mailers/devise_mailer.rb
  def confirmation_instructions(record, token, opts = {})
    send_mail(super)
  end
  def unlock_instructions(record, token, opts = {})
    send_mail(super)
  end
バックエンド修正
app/models/user.rb に追加
  attr_accessor :redirect_url # Tips: /users/auth/{update,sign_in}で使用
app/controllers/users/auth/registrations_controller.rb
  def update
    if params[:confirm_redirect_url].blank?
      return render './failure', locals: { alert: t('devise_token_auth.registrations.confirm_redirect_url_blank') }, status: :unprocessable_entity
    end
    if blacklisted_redirect_url?(params[:confirm_redirect_url])
      return render './failure', locals: { alert: t('devise_token_auth.registrations.confirm_redirect_url_not_allowed') }, status: :unprocessable_entity
    end
    @resource.redirect_url = params[:confirm_redirect_url]
    super
  end
confirm_redirect_urlパラメータのチェックと値を@resource(Users)にセットしています。
app/controllers/users/auth/sessions_controller.rb
  def create
    return render './failure', locals: { alert: t('devise_token_auth.sessions.bad_credentials') }, status: :bad_request if request.request_parameters.blank?
    if params[:unlock_redirect_url].blank?
      return render './failure', locals: { alert: t('devise_token_auth.sessions.unlock_redirect_url_blank') }, status: :unprocessable_entity
    end
    if blacklisted_redirect_url?(params[:unlock_redirect_url])
      return render './failure', locals: { alert: t('devise_token_auth.sessions.unlock_redirect_url_not_allowed') }, status: :unprocessable_entity
    end
    return render_create_error_bad_credentials if params[:email].blank? || params[:password].blank?
    super
  end
  private
  def find_resource(field, value)
    super
    @resource.redirect_url = params[:unlock_redirect_url] if @resource.present? && params[:unlock_redirect_url].present?
    @resource
  end
同様にunlock_redirect_urlパラメータのチェックと値を@resource(Users)にセットしています。
ただ、こちらは@resourceがセットされてない状態で、createが呼ばれるので、find_resourceをoverrideして、直後にセットするようにしました。
app/mailers/devise_mailer.rb
class DeviseMailer < Devise::Mailer
  layout 'mailer'
  # メールアドレス確認のお願い
  def confirmation_instructions(record, token, opts = {})
+    opts[:redirect_url] = record.redirect_url if record.is_a?(User) && record.redirect_url.present? # Tips: /users/auth/updateで使用
    send_mail(super)
  end
  # アカウントロックのお知らせ
  def unlock_instructions(record, token, opts = {})
+    opts[:redirect_url] = record.redirect_url if record.is_a?(User) && record.redirect_url.present? # Tips: /users/auth/sign_inで使用
    send_mail(super)
  end
controllerでセットしたURLをoptsに詰めれば、メールテンプレートで受け取れるようになります。
実際はテスト駆動で開発していますので、Specはコミットを参照してください。
