Rails7 APIモードの認証機能を実装するにあたり、Deviseのトークン認証を可能にするdevise_token_auth というライブラリを使用することにしました。
導入に少しハマったところなどもありましたので記事にまとめました。同じような構成を検討している方の参考になれば幸いです。
環境構成
フロントエンド:Next.js 12.1
バックエンド:Rails 7.0
開発環境:Docker、docker-compose
devise_token_authの準備
Gemのインストール
Gemfileに以下を記載してbundle install
します。
**Gemfile **
gem ' devise '
gem ' devise_token_auth '
gem ' devise-i18n '
gem ' rack-cors '
rack-cors はCORS(Cross-Origin Resource Sharing)を実現させるために必要となりますので、一緒にインストールします。
devise-i18n はメッセージの日本語化のために必要となるパッケージです。
インストールタスクの実行
deviseとdevise_token_authのインストールタスクを実行します。
今回はUserモデルに対してDeviseを適用したいと思います。
rails g devise:install
rails g devise_token_auth:install User auth
作成されるconfig/initializers/devise_token_auth.rb
というファイルに各種設定を記述します。
私の場合config.change_headers_on_each_request = false
のみ変更しました。
これ設定をすることでリクエストの都度トークンが変更されなくなるので、トークンの管理が楽になります。
routesとmigrationファイルの修正
今回は/api/v1/auth
というパス上に認証用のエンドポイントを作成したいと思うので以下のようにroutes.rbを修正します。
**config/routes.rb**
namespace :api do
namespace :v1 do
mount_devise_token_auth_for ' User ' , at : ' auth '
end
end
また、devise_token_auth:install
ではログイン日時などの情報をトラッキングするためのマイグレーション情報が作成されないようなので、以下のように手動で修正を加えます。
**db/migrate/xxxxx_devise_token_auth_create_users.rb**
t.integer :sign_in_count , default : 0 , null : false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
メッセージの日本語化
devise-i18nを使用してデフォルト英語のメッセージを日本語に変換させます。
rails g devise:i18n:locale ja
を実行すると/config/locales/devise.views.ja.yml
というファイルが作成されます。
このままでは日本語化されませんので、application_controller.rbに以下の定義を記載します。
**app/controllers/application_controller.rb**
before_action do
I18n .locale = :ja
end
CORSの設定
originsにフロントのホスト(localhost:8000)を記載して以下のようにCORSの設定ファイルを作成します。
exposeには%w[access-token uid client]
を記述しています。これによりクロスドメインでもヘッダー情報が公開されるようになります。
**config/initializers/cors.rb**
Rails .application.config.middleware.insert_before 0 , Rack ::Cors do
allow do
origins " localhost:8000 "
resource " * " ,
headers : :any ,
expose : %w[ access-token uid client ] ,
methods : [:get , :post , :put , :patch , :delete , :options , :head ]
end
end
各メソッドの動作検証
ここまで準備が完了しましたらrails db:migrate
を実行してdeviseの動作検証をしてきます。
devise_token_authはHTTPリクエストのヘッダーにuid、access-token、clientを付与する ことで認証が行われます。
ちなみにcurlに-iオプションをつけるとHTTPレスポンスのヘッダー情報が出力されますので便利です。
ユーザ登録
/
にPOSTでアクセスします
email、password、password_confirmationをparamsに設定して送信することでユーザが作成されます
curl localhost:3000/api/v1/auth -X POST -d ' {"email":"test@example.com", "password":"password", "password_confirmation": "password"} ' -H " content-type:application/json " -i
ユーザ削除
/
にDELETEでアクセスします
HTTPヘッダーにuid、access-token、clientを付与することで、該当のユーザが削除されます
curl http://localhost:3000/api/v1/auth -H " uid: test@example.com " -H " client: hpHyxPAgFmVxFA75uJOhYA " -H " access-token: glXm3iysPovJSi4q_ffpwQ " -X DELETE
ActionDispatch::Request::Session::DisabledSessionErrorについて
ちなみにRails7の場合、そのままの状態だと以下のエラーが発生します
ActionDispatch ::Request ::Session ::DisabledSessionError (Your application has sessions disabled. To write to the session you must first configure a session store):
Rails7より、APIモードのようにセッションを使用しない場合にセッションへのアクセスがあるとエラーを発生させる機構が備わったことによるエラーです。
deviseでは内部でセッションにアクセスしている箇所があるので当該エラーが発生する模様です。
私の場合、ワークアラウンドとして以下の処理を入れることで一旦回避させました。
根本対応されましたら、きちんと修正しておくことにします。
**app/controllers/application_controller.rb**
class ApplicationController < ActionController ::API
include DeviseTokenAuth ::Concerns ::SetUserByToken
include DeviseHackFakeSession
end
**app/controllers/concerns/devise_hack_fake_session.rb**
module DeviseHackFakeSession
extend ActiveSupport ::Concern
class FakeSession < Hash
def enabled?
false
end
def destroy
end
end
included do
before_action :set_fake_session
private
def set_fake_session
if Rails .configuration.respond_to?(:api_only ) && Rails .configuration.api_only
request.env[' rack.session ' ] ||= ::DeviseHackFakeSession ::FakeSession .new
end
end
end
end
参考: https://github.com/heartcombo/devise/issues/5443
ユーザ変更
/
にPUTでアクセスします
HTTPヘッダーにuid、access-token、clientを付与して該当のユーザを変更します
デフォルトはemailとpasswordのみ変更可能なので、必要に応じてconfigure_permitted_parametersにパラメータを追記します
curl http://localhost:3000/api/v1/auth -d ' {"password":"password", "name":"taro"} ' -H " content-type:application/json " -H " uid: test@example.com " -H " client: fy5z545K8TwgunV_D2LJXg " -H " access-token: B5z0QZ8jro5zZbqcuHAtow " -X PUT
**app/controllers/application_controller.rb**
before_action :configure_permitted_parameters , if : :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:account_update , keys : [:name ])
end
ログイン
/sign_in
にPOSTでアクセスします
認証が成功するとHTTPレスポンスヘッダーにuid、access-token、clientが付与されてきます。認証が必要なアクションに対してリクエストヘッダーそれらを付与することでアクセス可能になります
curl localhost:3000/api/v1/auth/sign_in -X POST -d ' {"email":"test@example.com", "password":"password"} ' -H " content-type:application/json " -i
access-token: OtzRYGvcy-W3zvXzQijB4Q
token-type: Bearer
client: r7zZ0MBaFSnNcGsiRZQetQ
expiry: 1657875966
uid: test @example.com
ログアウト
/sing_out
にDELETEでアクセスします
HTTPヘッダーにuid、access-token、clientを付与して該当のユーザがログアウトされます
curl http://localhost:3000/api/v1/auth/sign_out -H " content-type:application/json " -H " uid: test@example.com " -H " client: fy5z545K8TwgunV_D2LJXg " -H " access-token: B5z0QZ8jro5zZbqcuHAtow " -X DELETE
パスワードリセット
/auth/password
にPOSTでアクセスします
emailとredirect_urlをパラメータに付与することで、該当のアドレスに対してパスワードリセットメールが送信されます。メール内に記載されたパスワードリセットフォームのURLにredirect_urlが設定されます
devise_token_auth(v1.2.2)時点でなぜかrails g devise_token_auth:install_views
でメールテンプレートファイルを作成しなければredirect_urlが動作しない事象が発生しています。
curl http://localhost:3000/api/v1/auth/password -H " content-type:application/json " -d ' {"email":"hello@example.com", "redirect_url":"http://localhost:3000/pages"} ' -X POST
パスワードリセットメール
疎通確認
では実際に、認証が必要なエンドポイントを設けて実際に疎通確認をしてみます。
/api/v1/home
というエンドポイントを設けます。
ログインしている場合のみアクセスできるようにbefore_action :authenticate_api_v1_user!
を定義しています。authenticate_api_v1_userのメソッド名はdeviseがマウントされているpathによって動的に命名されますので、api/v1
という構成でない場合は読み替えてください。
Rails .application.routes.draw do
resources :pages , only : [:index ]
namespace :api do
namespace :v1 do
resources :home , only : [:index ]
mount_devise_token_auth_for ' User ' , at : ' auth '
end
end
end
class Api ::V1 ::HomeController < ApplicationController
before_action :authenticate_api_v1_user!
def index
render json : { message : ' hello ' }
end
end
tokenが正しい場合
curl http://localhost:3000/api/v1/home -H " content-type:application/json " -H " uid: hello@example.com " -H " client: NdQxhsz2PiRdBR25xIfzJg " -H " access-token: Zv5thbTxDEWEyn7Nhl_SLg " -X GET
{ " message " :" hello " }
tokenが正しくない場合
curl http://localhost:3000/api/v1/home -H " content-type:application/json " -H " uid: hello@example.com " -H " client: NdQxhsz2PiRdBR25xIfzJg " -H " access-token: hoge " -X GET
{ " errors " :[" ログインもしくはアカウント登録してください。 "] }
終わりに
今回の記事ではdevise_token_authのインストール方法と使い方をメインに説明しました。
次回はNext.jsとの繋ぎ込み部分に関してのセッション管理をどうするか、認証によるページ制御をどうするかについて説明しようと思います。
Next.jsとdevise_token_authを使って認証周りを実装する