ApiKeyによるAPIの認証を実装

以下の続き。
sorceryでログイン機能のAPIを作成 - Rails技術ブログ
ユーザー新規登録機能のAPIを作成 - Rails技術ブログ

①ApiKeyモデルを作成。
$ rails g model ApiKey user:references access_token:string expires_at:datetime
class CreateApiKeys < ActiveRecord::Migration[6.0]
  def change
    create_table :api_keys do |t|
      t.references :user, null: false
      t.string :access_token, null: false
      t.datetime :expires_at

      t.timestamps

      t.index ["access_token"], unique: true
    end
  end
end
$ rails db:migrate


②モデルを設定。
# app/models/api_key.rb

class ApiKey < ApplicationRecord
  belongs_to :user

  validates :access_token, uniqueness: true

  scope :active, -> { where('expires_at >= ?', Time.current) }

  # 初期化時にaccess_tokenとexpires_atを設定
  def initialize(attributes = {})
    super
    self.access_token = SecureRandom.uuid
    self.expires_at = 1.week.after
  end
end
# app/models/user.rb

class User < ApplicationRecord
.
.
.
  def activate_api_key!
    return api_keys.active.first if api_keys.active.exists?

    api_keys.create
  end
end


③新規登録とログインの際にApiKeyを作成するようにコントローラーを設定。

※ユーザーが期限内のApiKeyを持っている場合はそれを使用。

# app/controllers/api/v1/base_controller.rb

module V1
    class BaseController < ApplicationController       class BaseController < ApplicationController
.
.
.
      protected

      def set_access_token!(user)
        api_key = user.activate_api_key!
        response.headers['AccessToken'] = api_key.access_token
      end
.
.
.
# app/controllers/api/v1/authentications_controller.rb

module Api
  module V1
    class AuthenticationsController < BaseController
      def create
        @user = login(params[:email], params[:password])
        raise ActiveRecord::RecordNotFound unless @user

        json_string = UserSerializer.new(@user).serialized_json
        set_access_token!(@user)
        render json: json_string
      end
    end
  end
end
# app/controllers/api/v1/registrations_controller.rb

module Api
  module V1
    class RegistrationsController < BaseController
      def create
        @user = User.new(user_params)

        if @user.save
          json_string = UserSerializer.new(@user).serialized_json
          set_access_token!(@user)
          render json: json_string
        else
          render_400(nil, @user.errors.full_messages)
        end
      end

      private

      def user_params
        params.require(:user).permit(:name, :email, :password)
      end
    end
  end
end


④記事一覧、記事詳細APIを取得する際にAPIトークンで認証する。
# app/controllers/api/v1/base_controller.rb

module Api
  module V1
    class BaseController < ApplicationController
.
.
.
      # authenticate_or_request_with_http_tokenを使うために記載が必要。
      include ActionController::HttpAuthentication::Token::ControllerMethods

      protected

      def authenticate
        # トークンが誤っていると(HTTP Token: Access denied.)を返す。
        authenticate_or_request_with_http_token do |token, _options|
          @_current_user ||= ApiKey.active.find_by(access_token: token)&.user
        end
      end

      def current_user
        @_current_user
      end

      def set_access_token!(user)
        api_key = user.activate_api_key!
        response.headers['AccessToken'] = api_key.access_token
      end
.
.
.
    end
  end
end
# app/controllers/api/v1/articles_controller.rb

module Api
  module V1
    class ArticlesController < BaseController
      before_action :set_article, only: :show
      before_action :authenticate

      def index
        articles = Article.all
        json_string = ArticleSerializer.new(articles).serialized_json
        render json: json_string
      end

      def show
        options = { include: %i[user 'user.name' 'user.email'] }
        json_string = ArticleSerializer.new(@article, options).serialized_json
        render json: json_string
      end

      private

      def set_article
        @article = Article.find(params[:id])
      end
    end
  end
end



参考記事:
[Ruby on Rails]sorceryによる認証 – (5)APIでの認証 #3 モデル | Developers.IO
【Rails】RailsでAPIの簡単なトークン認証を実装する - Qiita