Edited at

Rails5から使えるhas_secure_tokenをMySQLで使うときのまとめ

More than 3 years have passed since last update.


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: バイナリ比較をする(大文字小文字を区別する)

日本語を表せる文字コードの場合はcibinのみが用意され、かつデフォルトが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で文字列カラムに対してcharsetcollationが指定できるようになりました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さんに感謝しましょう