はじめに
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のみに変更を加える。
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
sessions: 'auth/sessions'
}
実際に変更する内容は以下の通りです。
-
access-token
,client
,uid
をもとにtokenを生成 - tokenをcookieにセット
token生成、cookieセットの処理はconcernsとしてmoduleに切り出しています。
class Auth::SessionsController < DeviseTokenAuth::SessionsController
include Auth::JoinToken
end
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
に実装してしまいましょう。
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を使用することによるセキュリティ的な観点からは自己責任でお願いします!