has_secure_token
Rails5からAPI modeが実装されたのに伴って、has_secure_tokenがRailsにマージされて標準機能になりました。平たくいうとhas_secure_passwordのtokenバージョン、と思えば大丈夫です。
実装については該当のPullRequestを貼っておくのでご参照してください。
https://github.com/rails/rails/pull/18217
また、元々公開されていたGithub URLも合わせて貼っておきます。
https://github.com/robertomiranda/has_secure_token
使い方
下記のように定義すれば token
カラムに対して、has_secure_token
メソッドで生成されるtokenがUserオブジェクトを生成するときに挿入されるようになります。
class User < ApplicationRecord
has_secure_token
end
挿入されるカラムを変更したいときは明示的に指定すればOKです。
下記の例だと auth_token
カラムに対して動作するようになります。
class User < ApplicationRecord
has_secure_token :auth_token
end
オブジェクトが保存されると自動でtokenが生成されます。
>> user = User.new(name: 'kobito')
>> user.save
=> true
>> user.auth_token
=> "fppFh6xAsGfNTiDxzreW283f"
tokenの再生成もできます。
>> user.regenerate_auth_token
=> true
>> user.auth_token
=> "Nr8MmABvZSceLLdaEf3ALmW4"
Generatorから生成するときは:token
と定義してやれば、ModelファイルとMigrationファイルが生成できます。
$ rails g model user name:string auth_token:token
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :name
t.string :auth_token
t.timestamps
end
add_index :users, :auth_token, unique: true
end
end
MySQLにおける問題点
MySQLは文字列の照合順序というパラメータを持っていて、下記の3パターンが用意されています。
name | description |
---|---|
ci | Case Insensitive: 大文字小文字を区別しない |
cs | Case Sensitive: 大文字小文字を区別する |
bin | Binary: バイナリ比較をする(大文字小文字を区別する) |
日本語を表せる文字コードの場合はci
とbin
のみが用意され、かつデフォルトがci
になっています。そのためデフォルトでは大文字小文字を区別せず比較してしまいます。1
使い方の例で示したようにhas_secure_tokenで生成されるtokenは大文字小文字を区別する前提で発行されているのでci
のままで利用するとtokenの衝突が発生する原因となってしまいます。
mysql> SELECT auth_token FROM users where id = 1
+--------------------------+
| auth_token |
+--------------------------+
| p7YL2xQL2vaod85sn7j8Mui8 |
+--------------------------+
mysql> SELECT id from users where auth_token = 'p7YL2xQL2vaod85sn7j8Mui8'
+----+
| id |
+----+
| 1 |
+----+
-- 全て大文字にしたtokenでも取得できてしまう
mysql> SELECT id from users where auth_token = 'P7YL2XQL2VAOD85SN7J8MUI8'
+----+
| id |
+----+
| 1 |
+----+
そのため、tokenを挿入するカラムをbin
に指定してやる必要があります。(特に影響がないのであれば、テーブル単位で設定しても良いと思います)
Rails5から@kamipoさんのご尽力でMySQLで文字列カラムに対してcharset
やcollation
が指定できるようになりました2
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :name
t.string :auth_token, charset: 'utf8', collation: 'utf8_bin'
t.timestamps
end
add_index :users, :auth_token, unique: true
end
end
https://github.com/rails/rails/issues/20133 のPullRequestで上記で挙げた問題があるのでSecureRandom.hex
使った方がいいんじゃないか、という提案がされています。
PRに対する反応を見た感じ、case sensitiveで対応すればいいよね、という流れなのでMySQLでhas_secure_tokenを利用される場合はcollationの指定を忘れずにすることが必須となりそうです。
まとめ
- Rails5からActiveRecordでhas_secure_tokenというtoken生成メソッドが使えるようになった
- has_secure_passwordと同じ感覚で使える
- MySQLで利用する場合は文字列の照合順序を
bin
にすることを忘れずに! - @kamipoさんに感謝しましょう