9
10

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 auth tokenをcookie認証にカスタマイズする

Last updated at Posted at 2022-09-06

はじめに

APIの認証で使用されるgemdevise auth tokenをデフォルトで採用されているBearer認証からcookie認証へカスタマイズする方法を紹介します。
devise auth tokenの基本的な使用方法は以下記事などを参考にしてください。

デフォルトの認証

before_action :authenticate_user!

devise_token_authでは、上記のようにcontrollerで記述した場合実行前に認証が行われる。
通常認証の方法はsign-inのリクエストを送信したレスポンスHeaderにあるaccess-token, client, uidを毎回リクエストヘッダに追加することで、認証することができる。( RFC 6750 Bearer Token に準拠している。)

フロントエンドリクエスト例(axios)

axios.post("/sample", requestBody, { headers: { 
  access-token: "access-token",
  client: "client",
  uid: "uid"
}});

毎回3つもヘッダーに追加するのは面倒じゃないですか?私は面倒だと思いました。
そこでフロント側では何も設定しなくて良いようにcookieを使用した方法にしたいと思ったので実際にやってみました。

実装

まず、sign_in時のレスポンスを変更します。routesで一部controllerに変更を加える設定を追加します。sign_in時には、devise_token_auth/sessions#createのactionが動いているので、sessionsのcreateのみに変更を加える。

routes.rb
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
    sessions: 'auth/sessions'
}

実際に変更する内容は以下の通りです。

  • access-token, client, uidをもとにtokenを生成
  • tokenをcookieにセット

token生成、cookieセットの処理はconcernsとしてmoduleに切り出しています。

controller/auth/session_controller.rb
class Auth::SessionsController < DeviseTokenAuth::SessionsController
  include Auth::JoinToken
end
controllers/concerns/auth/join_token.rb
module Auth::JoinToken
  include ActionController::Cookies
  extend ActiveSupport::Concern

  included do
    prepend_after_action :join_tokens, only: [:create]
  end

  private

  def join_tokens
    return if response.headers['access-token'].nil?

    cookies[:token] = {
      value: encode_access_token,
      httponly: true,
      expires: 30.days
    }

    response.headers.delete_if { |key| auth_headers_data.include?(key) }
  end

  def auth_headers_data
    {
      'access-token' => response.headers['access-token'],
      'client' => response.headers['client'],
      'uid' => response.headers['uid']
    }
  end

  def encode_access_token
    CGI.escape(Base64.encode64(JSON.dump(auth_headers_data)))
  end
end

このようにすることでtokenがcookieにセットされブラウザに保存されます。
フロント側からリクエスト送信する時は以下のように設定するだけでaccess-token, client, uidを毎回リクエストヘッダに追加する必要がなくなりました!

export const apiClient = axios.create({
  withCredentials: true,
});

ここまででaccess-token, client, uidを毎回リクエストヘッダに追加しないといけない問題は解決しましたがバックエンド側でcookieのtokenからaccess-token, client, uidを取得する処理を実装しないといけません。結局認証には上記3つが必要だからです。
全てのコントローラーで継承するApplicationControllerに実装してしまいましょう。

controllers/application_controller.rb
class ApplicationController < ActionController::API
  include DeviseTokenAuth::Concerns::SetUserByToken
  include ActionController::Cookies
  before_action :split_token

  private

  def split_token
    return if cookies[:token].nil?

    token = JSON.parse(Base64.decode64(CGI.unescape(cookies[:token])))
    request.headers['access-token'] = token['access-token']
    request.headers['client'] = token['client']
    request.headers['uid'] = token['uid']
  end
end

認証したい箇所では以下のように記述することで実行できます!

before_action :authenticate_user!

まとめ

これでカスタマイズ完了しました!フロントエンド側の手間がかなり減ったかなと思います。
cookieを使用することによるセキュリティ的な観点からは自己責任でお願いします!

9
10
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
9
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?