## generates_token_forとは `generates_token_for`は、特定の目的を持つトークンを生成し、そのトークンからレコードを検索・検証するための機能です。 Rails 7.1で標準機能として導入されました。 ### 特長 * **DBへのデータ保存不要**:トークンを保存するためのデータベースのカラムやテーブルを追加せずに利用できます。 * **目的別に分離**:目的(purpose)ごとにトークンを分離できるため、別目的で生成されたトークンを使い回すことができません。 * **失効条件を設定可能**:有効期限とモデルのデータ変化による失効条件を柔軟に設定できます。 * **改ざん耐性**:署名付きのトークンのため、改ざん耐性があります。 ## 使いどころ:一時的に有効なURLの送付 以下のような「一時的で目的が限定されたトークン付きURL」を送付する場面に適しています。 * メールアドレス確認リンク * 招待リンク * マジックリンク(パスワードなしログイン) * 一時アクセスリンク なお、APIの認証トークンのような永続的な用途には向きません。 Railsで永続的なトークンを扱いたい場合は、`has_secure_token`などの利用を検討してください。 ### 注意:パスワードリセットには専用APIあり Rails 8.0以降で`has_secure_password`を利用している場合、パスワードリセット用トークンを扱う専用APIが自動的に提供されます。そのため、パスワードリセットの用途に限っては、自分で`generates_token_for`を定義する必要はありません。(詳しくは後述の補足を参照してください) ## 使い方(メールアドレス確認の例) ここでは「メールアドレス変更時の確認リンク」を題材に、基本的な使い方を説明します。 ### 基本の流れ(定義・生成・検証) まず、モデルに`generates_token_for`を用いて、トークンの用途名(ここでは`:email_verification`)を定義します。 なお、用途名が異なっていれば複数定義することも可能です。 ```ruby class User < ApplicationRecord generates_token_for :email_verification end ``` トークンを生成する際は、対象のインスタンスに対して`generate_token_for`を呼び出します。 > 注意:モデルで定義する際は複数形の`generates_token_for`ですが、 > インスタンスから呼び出す際は単数形の`generate_token_for`になる点に注意してください。 ```ruby user = User.first # 対象のレコードを取得(ここでは`first`で代替) token = user.generate_token_for(:email_verification) # => "BAhJIi..." (生成されたトークン文字列) ``` 生成したトークンは、例えば以下のようにURLパラメータに載せてメールなどで送付するのが典型的な流れです。 ```ruby # 例:メイラー等でのURL生成 email_verifications_url(token: token) # => "https://example.com/email_verifications?token=BAhJIi..." ``` 受け取ったトークンからレコードを検索・検証するには、クラスメソッドの`find_by_token_for`を使います。 有効なトークンならUserインスタンスが返り、無効なトークンや期限切れのトークンの場合は`nil`が返ります。 ```ruby user = User.find_by_token_for(:email_verification, token) ``` ### 有効期限による失効 定義時に`expires_in`オプションを付与することで、トークンに有効期限(例:15分間)を設けることができます。 ```ruby class User < ApplicationRecord generates_token_for :email_verification, expires_in: 15.minutes end ``` ### モデルのデータ変化による失効 `generates_token_for`は、「一度状態が変わったら、古いトークンを使わせない」挙動を簡単に実装できます。 例えば、新しいメールアドレス確認用に生成されたトークンを、メールアドレスの変更が完了した後に無効化すれば、万が一流出しても悪用を防げます。 無効化の仕組みは「定義時のブロックの戻り値をトークンに埋め込み、検証時にその値が変わっていれば無効とみなす」というものです。 ```ruby class User < ApplicationRecord generates_token_for :email_verification, expires_in: 15.minutes do # トークン生成時と検証時で`email`の値が異なれば(=メールアドレスが変更完了していれば)、 # 有効期限内であっても無効になる email end end ``` #### 注意:ブロックの戻り値に機密情報を含めない ブロック内で返した値は、改ざん耐性はあるものの、暗号化されるわけではなく、デコードすれば読める形でトークンに埋め込まれます。 そのため、ブロックの戻り値には機密情報を含めてはなりません。 ## 補足:パスワードリセット専用API Rails 8.0以降では、モデルで`has_secure_password`を利用している場合、パスワードリセットトークン用の以下のメソッドがデフォルトで追加されます。 * `password_reset_token`:トークン生成(インスタンスメソッド) * `find_by_password_reset_token`:トークンからの検索と検証(クラスメソッド) * `find_by_password_reset_token!`:同上(無効時に例外を発生させるバージョン) ### Rails 8.1で追加されたトークン有効期限の機能 さらにRails 8.1以降では、トークンの有効期限を柔軟に扱うための機能が追加されています。 `has_secure_password`の呼び出し時に`reset_token`オプションを通して有効期限を変更できるようになりました。 ```ruby class User < ApplicationRecord has_secure_password reset_token: { expires_in: 1.hour } end ``` また、以下のメソッドが追加されました。 * `password_reset_token_expires_in`:設定されている有効期限の取得(インスタンスメソッド) ## 参考情報 * [Rails APIドキュメント:generates_token_for](https://api.rubyonrails.org/classes/ActiveRecord/TokenFor/ClassMethods.html#method-i-generates_token_for) * [Rails APIドキュメント:has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password) * [GitHub:has_secure_passwordの実装コード](https://github.com/rails/rails/blob/main/activemodel/lib/active_model/secure_password.rb)