フロントエンドをreactなどでSPA的な実装をしたいときにバックエンドのAPIを最低限のセキュリティにも配慮した形で実装したいと思い、devise_token_authでトークンベースの認証方法を採用したいと考えて、devise_token_authについて試行錯誤した結果を整理した記事です。
初心者ゆえ、できる限りシンプルな形での実装から練習を始めたかったのですが、発展的な記事はよく見かけるものの「基本のき」から説明している記事はあまり見かけなかったので、Qiitaに覚書きがてら書いている次第です。
変な部分がありましたら、ぜひご指摘ください。
実行環境と要件
実行環境は以下の通り
- Rails 7.0.4.3
- devise 4.9.2
- devise_token_auth 1.2.1
要件としては、とにかくシンプルなAPIで練習するために
- User認証機能(新規登録、ログイン)
- deviseのデフォルトであるメールアドレスとパスワードでの登録およびログイン
- おまけでAPIのバージョン管理をできるように
といったところを実現することを目指します。
実装フロー
以下の通り、実行していきます。
-
新しいRailsプロジェクトを作成。
rails new my_api --api
-
Gemfileにて
rack-cors
を有効化し、devise
とdevise_token_auth
を追加。gem 'rack-cors' #コメントアウトを外す gem 'devise' gem 'devise_token_auth'
-
Gemのインストールを実行。
bundle install
-
Devise のインストールジェネレータを実行したのちに、Devise Token Authのインストールジェネレータを実行。(devise_token_authの公式ドキュメントには載っていないが、先にdeviseをインストールしておかないとエラーを吐く)
#先にdeviseをインストール rails g devise:install #続いてdevise_token_authをインストール(ここでUserモデルやマイグレーションファイルなどが生成) rails g devise_token_auth:install User auth
-
マイグレーションを実行してデータベースに必要なテーブルを作成。
rails db:migrate
-
devise_token_authの設定を変更。
initializer/devise_token_auth.rbにて、以下の3つを設定する。
# frozen_string_literal: true DeviseTokenAuth.setup do |config| config.change_headers_on_each_request = false#コメントアウト外してfalseに変更 config.token_lifespan = 2.weeks#コメントアウト外す config.headers_names = {:'access-token' => 'access-token',#コメントアウト外す :'client' => 'client', :'expiry' => 'expiry', :'uid' => 'uid', :'token-type' => 'token-type'}
さらに同じinitialize/devise_token_auth.rbファイルにて、実はconfig.headers_namesの設定で、以下のauthorizationの設定を追加しないとエラーを吐く。
config.headers_names = {:'access-token' => 'access-token', :'client' => 'client', :'expiry' => 'expiry', :'uid' => 'uid', :'token-type' => 'token-type', :'authorization' => 'authorization'#この1行を追加 }
表示されるエラー内容としては、以下の通り。
NoMethodError: undefined method `downcase' for nil:NilClass
-
ルーティングを設定。
# config/routes.rb Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'auth' end
-
User
モデルでdevise
とdevise_token_auth
を設定します。# app/models/user.rb class User < ApplicationRecord # Include default devise modules. devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable # Include additional devise modules. include DeviseTokenAuth::Concerns::User end
-
サーバーを起動します。
rails s
以上で、最もシンプルな形のdevise_token_authを使ったAPIはこれで実装できるはず。
この場合は、ユーザー登録とログインのためのエンドポイントは以下の通りとなる。
- 新規登録:
POST /auth
- ログイン:
POST /auth/sign_in
これらのエンドポイントに必要なパラメータを送信し、ユーザーの作成とログインを行うことができます。
おまけ
APIに関してはバージョン管理するのが一般的なようで、「v1」「v2」などの形で名前空間を作って管理しているのをよく見かけるので、その実装手順もメモしておきます。
1〜6までの手順は同じ。
7.ルーティング設定
バージョン管理するとなると、新規登録のエンドポイントが
/v1/auth
に変わるので、まずはルーティングの変更が必要。
# config/routes.rb
Rails.application.routes.draw do
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
end
end
8.コントローラの作成と設定
ルーティングが変わったことで、コントローラの読み込み先が変わったので、その変更を加えます。
devise_token_auth
はデフォルトではapplication_controller.rb
のコントローラしか生成しません。
その中身は以下の通り。
class ApplicationController < ActionController::API
include DeviseTokenAuth::Concerns::SetUserByToken
end
ユーザーの新規登録などのコントローラはクラスを継承することで動作するようになっていることがわかります。
なので、新しく今のルーティングに合わせたコントローラを作成し、そこに必要なクラスを継承させることが必要です。
まずはコントローラを作成するために以下のコマンドを実行します。
rails g controller v1/auth/registrations
当初私が参考にしていたサイトではここのコントローラファイルの作成ディレクトリをv1/registrations
で指定していたので、うまく読み込めずにエラーが出て、半日ぐらいハマってしまいました。。。きっとあれば誤植。。。
ルーティングの通りのディレクトリを指定しないとダメなので一応要確認です。
これが済んだら、ディレクトリ構成は以下の感じになっているかと思います。
続いてregistrationsコントローラの中身を整えます。
たぶんデフォルトでは以下のクラスが設定されているはずです。
class V1::Auth::RegistrationsController < ActionController::API
end
これを以下のようにDeviseTokenAuth::RegistrationsController
クラスを継承させるように変更することで、ルーティングに応じたコントローラの設定になります。
class V1::Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
end
9.ルーティングの修正
このようにコントローラの設定をいじったことで、ルーティングを再度修正する必要がある場合があります。
コントローラの読み込み先を明示してあげる必要があるみたいです。(これは環境によるのかちょっと不明)
Rails.application.routes.draw do
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
registrations: 'v1/auth/registrations'
}
end
end
ここまでの設定ができたら、apiがきちんと動くかpostmanなどで確認してみましょう。
ちゃんとstatus: 200 OK
が返ってきていますので、無事実装完了です!
調べるのにかなり時間かかった
ということで初心者が試しに動かしてみる、というレベルの記事がなかなか見当たらなかったので書いてみました。
ここまで調べて安定して実装できるようになるまでにかなり時間かかってしまいました。
しかしいい勉強になりました。
参考