【Rails】devise auth tokenをcookie認証にカスタマイズする

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

https://qiita.com/Masahiro_T/items/6bc49a625b437a7c2f45

https://qiita.com/tomokazu0112/items/5fdd6a51a84c520c45b5

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

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

```javascript
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`を毎回リクエストヘッダに追加する必要がなくなりました！
```javascript
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
```
認証したい箇所では以下のように記述することで実行できます！
```ruby
before_action :authenticate_user!
```
# まとめ
これでカスタマイズ完了しました！フロントエンド側の手間がかなり減ったかなと思います。
cookieを使用することによるセキュリティ的な観点からは自己責任でお願いします！