[Rails-API] sorceryによるAPIでの認証の実装①

学習記録

はじめに

rails-APIでもcurrent_userを使えるようにするために、ログイン時/新規登録時のレスポンスにaccess_tokenを付与しましょう。

ApiKeyモデル作成

> rails g model ApiKey user:references access_token:string expires_at:datetime

マイグレーションファイル確認

NOT NULL制約や、t.index ["access_token"], unique: true の追記をしましょう。

class CreateApiKeys < ActiveRecord::Migration[6.0]
  def change
    create_table :api_keys do |t|
      t.references :user, null: false, foreign_key: true
      t.string :access_token, null: false
      t.datetime :expires_at

      t.timestamps

      t.index ["access_token"], unique: true
    end
  end
end

ApiKeyモデルに追記

作成されたapi_key.rbに、必要な記述を追記します。

class ApiKey < ApplicationRecord
  DEFAULT_EXPIRES_WEEK = 1.week
  belongs_to :user

  validates :access_token, uniqueness: true

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

  def initialize(attributes = {})
    super
    self.access_token = SecureRandom.uuid
    self.expires_at = DEFAULT_EXPIRES_WEEK.after
  end
end
  • DEFAULT_EXPIRES_WEEK = 1.week 1.weekを定数にしています。
  • scope :active, -> { where('expires_at >= ?', Time.current) }
    こちらはscopeですが、SQLインジェクションを防ぐ書き方の1種で、プレースホルダーを使った書き方です。 ?はプレースホルダーと呼ばれ、第二引数で指定した値が置き換えられます。 下の場合は、Time.currentの部分が?に置き換えられます。
scope :active, -> { where('expires_at >= ?', Time.current) }
# 上のコードは下記のコードと同じ
scope :active, -> { where('expires_at >= Time.current') }

下記ではinitializeを設定しています。インスタンスが生成された際に呼び出されるメソッドです。

  def initialize(attributes = {})
    super
    self.access_token = SecureRandom.uuid
    self.expires_at = DEFAULT_EXPIRES_WEEK.after
  end

ActiveModelのルールとして、initialize時に何か処理を行う際にはsuperを呼び出して、attributesのhash値を渡してます。

If for some reason you need to run code on initialize, make sure you call super if you want the attributes hash initialization to happen. 和訳:何らかの理由で初期化時にコードを実行する必要がある場合、属性ハッシュの初期化を行いたいのであれば、必ず super を呼び出すようにしてください。
出典:https://api.rubyonrails.org/classes/ActiveModel/Model.html

initializeメソッドでattributesにハッシュを渡すときは、superを呼ぶこと。とおぼえておくと良いでしょう。

Userモデル追記

has_many :articles
  has_many :comments
  has_many :social_profiles
  has_many :api_keys # 追記

  validates :name, presence: true
  validates :email, presence: true, uniqueness: true

  def activate_api_key! # 追記
    return api_keys.active.first if api_keys.active.exists?

    api_keys.create
  end
end
  • active_api_key!
    ユーザーが既にvalidなApiKeyを持っている場合はそのApiKeyを使用するという処理をするメソッドです。 そうでなければ、新しいApiKeyを作成します。

authenticationsコントローラー

ログインのタイミングでApiKeyを作成する処理を追記します。 作成したApiKeyaccess_tokenHeaderに含めましょう。

    raise ActiveRecord::RecordNotFound unless @user

    json_string = UserSerializer.new(@user).serialized_json
    api_key = @user.activate_api_key! # 追記
    response.headers['AccessToken'] = api_key.access_token # 追記
    render json: json_string
  end
end

registrationsコントローラー

新規登録のタイミングでApiKeyを作成する処理を同様に追記します。

if @user.save
  json_string = UserSerializer.new(@user).serialized_json
  api_key = @user.activate_api_key! #追記
  response.headers['AccessToken'] = api_key.access_token #追記
  render json: json_string
else
  render_400(nil, @user.errors.full_messages)

参考

ActiveModel::Model
Active Model Basic Model Allows implementing models similar to ActiveRecord::Base.

コメント

タイトルとURLをコピーしました