くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

devise_token_authで認証機能を作成する

今回はdevise_token_authを使っての認証機能について解説しようと思います。

devise_token_authとは?

トークンベースの認証を行いたい場合devise_token_authというGemを使います。

トークンベースの認証とは、ユーザー情報を確認し、代わりに一意のアクセストークンを受け取ることを可能にするプロトコルです。 トークンの存続期間中、ユーザーはトークンが発行されたWebサイトやアプリにアクセスできます。 同じトークンで保護されたWebページ/アプリ/リソースに再度アクセスするたびに、資格情報を再入力する必要はありません。
認証トークンが有効である限り、ユーザーはアクセスを保持します。ユーザーがログアウトすると認証トークンは無効になります。

Railsではdeviseをログインなどで使う場合もあると思います。 deviseはCookieベースの認証を行う場合に使用します。

github.com

devise_token_authを使えるようにする

ここでは、devise_token_authを使えるようにします。

今回はDBはあらかじめ作成している状態から進めたいと思いますのでご了承ください。

devise、devise_token_auth、rack-corsの3つを使うので、3つをGemfileに追加しましょう。 deviseを先にインストールしていないとエラーになることがあるので、先にdeviseをインストールしましょう。

gem 'rack-cors'
gem 'devise'
gem 'devise_token_auth'

bundle installするとインポートできているはずです。

ただし、devise_token_authは次のコマンドでインストールしてください。

$ rails g devise_token_auth:install User auth

devise_token_auth.rbの設定

成功すると、config/initializers/devise_token_auth.rbというファイルが作成されているはずです。

ファイルの中身を確認して次の変更を行なってください(場合に応じて)。

// コメントを外してtrueからfalseに変更する
config.change_headers_on_each_request = false 

// コメントをはずす
config.token_lifespan = 2.weeks 

// コメントを外す
config.headers_names = {:'access-token' => 'access-token',
                        :'client' => 'client',
                        :'expiry' => 'expiry',
                        :'uid' => 'uid',
                        :'token-type' => 'token-type' }

config.change_headers_on_each_requestは trueの場合、リクエストごとにtokenを新しくする必要があるという設定になります。 毎回、トークンが変更されるという動きは今回は期待していないので、falseに変更します。

token_lifespanはtokenの有効期間を定義します。今回は、2週間に設定しておきます。

headers_namesは認証用ヘッダーの名前の定義です。必要でない限り、変更しなくて良いと思います。

Userモデルの設定

devise_token_authの導入に成功していれば、db/migrate/日付_devise_token_auth_create_users.rbというマイグレーションファイルが作成されていると思います。 デフォルトである程度定義されていますが、必要に応じてカラムを追加してください。

class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[7.0]
  def change
    
    create_table(:users) do |t|
      ## Required
      t.string :provider, :null => false, :default => "email"
      t.string :uid, :null => false, :default => ""

      ## Database authenticatable
      t.string :encrypted_password, :null => false, :default => "password"

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean  :allow_password_change, :default => false

      ## Rememberable
      t.datetime :remember_created_at

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at

      ## User Info
      t.string :name
      t.string :email

      ## Tokens
      t.json :tokens

      t.timestamps
    end

    add_index :users, [:email, :encrypted_password],   unique: true
    add_index :users, [:uid, :provider],     unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

作成したら、マイグレーションを行います。

$ rails db:migrate

さらに、ユーザーモデルを修正していきます。

models/user.rbというファイルがあるはずなので下記のように修正します。

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end

deviseではモジュールの設定を定義して、使える機能を定義しています。 下記のような機能があるので、使用したいものを羅列していきます。

機能 概要
database_authenticatable パスワードを暗号化してDBに登録する機能
registerable 登録処理を通してユーザーをサインアップする機能。ユーザーに自身のアカウントを編集したり削除することを許可します。
recoverable パスワードをリセットし、通知する機能
rememberable 保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除する機能
trackable サインイン回数や、サインイン時間、IPアドレスを記録します
validatable Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます
confirmable メールに記載されているURLをクリックして本登録を完了するという認証機能
lockable 一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります
timeoutable 一定時間活動していないアカウントのセッションを破棄します
omniauthable TwitterFacebookなどの認証機能

application_controller.rbの設定

app/controllers配下にapplication_controller.rbが作成されていると思うので、次のように変更してください。

class ApplicationController < ActionController::Base
        // 追加
        include DeviseTokenAuth::Concerns::SetUserByToken
end

上記を追加することでSetUserByTokenという拡張機能を使えるようになります。

DeviseTokenAuth::Concerns::SetUserByTokenは、ユーザーのトークンを使用して認証された場合に、current_useruser_signed_in?などのヘルパーメソッドを提供するためのコードが含まれたモジュールです。 このモジュールをApplicationControllerに含めることで、全てのコントローラーでこれらのヘルパーメソッドを使用できるようになります。

application_controller.rbの設定

config/application_controller.rbではexpose: ["access-token", "expiry", "token-type", "uid", "client"]を設定して、フロントエンドでもトークンの情報を確認できるようにします。

config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins ENV["API_DOMAIN"]
        resource '*',
        headers: :any,
        expose: ["access-token", "expiry", "token-type", "uid", "client"],
        methods: [:get, :post, :put, :patch, :delete, :options, :head]
     end
end

ルーティングの設定

ルーティングの設定のために、config/routes.rbに変更を加えます。

Rails.application.routes.draw do
  // 追加
  mount_devise_token_auth_for 'User', at: 'auth', controllers: {
    registrations: 'signup'
  }
end

registrations: 'signup'のsignupは適時変更してください。コントローラー名になります。 他の記事では、'auth/registrations'となっていることが多いです。

会員登録とログイン

ここからは実際に会員登録やログインの処理を見ていこうと思います。

まず、ここまで実行していれば、ルーティングは行われているはずなので、確認してみましょう。

rake routesを行うと次のように表示されるはずです。(表形式にしています)

Prefix Verb URI Pattern Controller#Action
new_user_session GET /auth/sign_in(.:format) devise_token_auth/sessions#new
user_session POST /auth/sign_in(.:format) devise_token_auth/sessions#create
new_user_registration GET /auth/sign_up(.:format) signup#new
user_registration POST /auth(.:format) signup#create

このように一部抜粋なのですが、ルーティングされているので、その情報をもとにして進めます。

Controllerの修正

まずは会員登録についてです。 会員登録はURIパターンがPOST /auth(.:format)なのでSignUpControllerを作成する必要があります。

class SignupController < DeviseTokenAuth::RegistrationsController
  def sign_up_params
    params.permit(:email, :password, :password_confirmation)
  end
end

上記のようにすることで、DeviseTokenAuthのRegistrationsControllerを継承しています。 そして、DeviseTokenAuth::RegistrationsControllerで定義されているsign_up_paramsの設定を更新しています。

フロントエンド(今回はVue.js)では次のように呼び出します。

async signUp () {
            try {
                const res = await axios.post(import.meta.env.VITE_APP_API_BASE + '/auth', {
                    email: this.email,
                    password: this.password,
                    password_confirmation: this.passwordConfirmation
                })

                console.log(response.headers["access-token"])
                console.log(response.headers["client"])
                console.log(response.headers["uid"])
               
            } catch (error) {
                console.log({ error })
            }
        }

成功すれば、responseの中にトークンの情報があるはずなので以降はトークンの情報をヘッダーに付与してリクエストを行います。

ログインはURIパターンがPOST /auth/sign_in(.:format)なので、devise_token_auth/sessions#createを使用します。 ルーティングの際に、mount_devise_token_auth_forを使ってUserモデルに対してDevise Token Authをマウントしたので、devise_token_auth/sessions#createなどのデフォルトのアクションが自動的に使えます。
カスタマイズする場合には継承したクラスを作成するという流れになるのですが、IDとパスワードでログインするだけであれば特に継承する必要はないと思います。

フロントエンドでは次のように呼び出します。

 async login() {
            try {
                const response = await axios.post(import.meta.env.VITE_APP_API_BASE + '/auth/sign_in', {
                    email: this.email,
                    password: this.password,
                })
                console.log(response.headers["access-token"])
                console.log(response.headers["client"])
                console.log(response.headers["uid"])
            } catch (error) {
                console.log({ error })
            }
        }

大まかに解説しましたが、認証機能はどのアプリケーションでも使用すると思うので、ぜひ細かく調べてみてください!

参照

nomad.office-aship.info

qiita.com

qiita.com

zenn.dev

poinorou.hatenablog.com

github.com

blog.furu07yu.com

qiita.com

serip39.hatenablog.com

qiita.com

www.webcyou.com

qiita.com

https://qiita.com/kskumgk63/items/aa581b6b3f8c66fa82e2qiita.com

github.com