Ruby
Rails
Rails6

Rails6 のちょい足しな新機能を試す11(has_secure_password 編)


はじめに

Rails 6 に追加されそうな新機能を試す第11段。 今回のちょい足し機能は、 has_secure_password 編です。

has_secure_password にオプションとしてカラムを指定できるようになりました。

記載時点では、Rails は 6.0.0.rc1 です。 gem install rails --prerelease でインストールできます。



$ rails --version
Rails 6.0.0.rc1


プロジェクトを作成する

rails プロジェクトを作成します。

$ rails new sandbox6_0_0rc1

$ cd sandbox6_0_0rc1


Gemfile を編集して bcrypt gem を追加する

has_secure_password を使うために bcrypt gem を追加します。

Gemfile の bcrypt の行を有効にします。


Gemfile

# Use Active Model has_secure_password

gem 'bcrypt', '~> 3.1.7'

bundle を実行します

$ bundle


User モデルを作成する

User モデルを作成します。

このとき、password の他に token 用の digest カラムを追加します。

$ bin/rails g model User name password_digest token_digest

$ bin/rails db:create db:migrate


User モデルを編集する

User model を編集し、 has_secure_password を追加します。

:token オプションを追加したものとオプションを省略したもの2行を追加します。

オプションを省略した場合は従来と同じですね。


app/models/user.rb

class User < ApplicationRecord

has_secure_password # これは今までと同じ
has_secure_password :token, validations: true # こちらは新機能
end


has_secure_password の機能を確認する

rails コンソールから確認してみます。



$ bin/rails c
Running via Spring preloader in process 393
Loading development environment (Rails 6.0.0.rc1)

オプションなしの方は今までと変わりません。 password, password_confirmation などのメソッドが使えます。

irb(main):001:0> u = User.new

=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):002:0> u.password
=> nil
irb(main):003:0> u.password_confirmation
=> nil

オプションを指定した :token に合わせて、 token token_confirmation などのメソッドが追加されます。

irb(main):004:0> u.token

=> nil
irb(main):005:0> u.token_confirmation
=> nil

validation を試してみると password だけではなく token についてもチェックしていることがわかります。

irb(main):006:0> u.valid?

=> false
irb(main):007:0> u.errors.full_messages
=> ["Password can't be blank", "Token can't be blank"]

tokentoken_confirmation が一致しているかどうかのチェックもしてくれます。

irb(main):008:0> u.password = 'password'

=> "password"
irb(main):009:0> u.password_confirmation = 'pass'
=> "pass"
irb(main):010:0> u.token = 'token'
=> "token"
irb(main):011:0> u.token_confirmation = 'tok'
=> "tok"
irb(main):012:0> u.valid?
=> false
irb(main):013:0> u.errors.full_messages
=> ["Password confirmation doesn't match Password", "Token confirmation doesn't match Token"]

password_confirmation, token_confirmation を設定してデータベースに保存してみましょう。

irb(main):014:0> u.password_confirmation = 'password'

=> "password"
irb(main):015:0> u.token_confirmation = 'token'
=> "token"
irb(main):016:0> u.name = 'user1'
=> "user1"
irb(main):017:0> u.save
(0.3ms) BEGIN
User Create (0.5ms) INSERT INTO "users" ("name", "password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["name", "user1"], ["password_digest", "$2a$10$uvvJuUHV8fNQ/0GRRtyewuboiAEUpD4Q1.0/coyWWhVxOivHnIMia"], ["token_digest", "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe27/EE56UpC"], ["created_at", "2019-05-03 23:04:03.971339"], ["updated_at", "2019-05-03 23:04:03.971339"]]
(7.3ms) COMMIT
=> true

今保存したデータを読み直します。



irb(main):022:0> u = User.last
User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">

password は今まで通り authenticate で認証できます。

irb(main):023:0> u.authenticate('pass')

=> false
irb(main):024:0> u.authenticate('password')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">

tokenauthenticate_token で認証できます。

irb(main):025:0> u.authenticate_token('password')

=> false
irb(main):026:0> u.authenticate_token('token')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">


validations: false にしたときは?

validations: false のときは、validation のチェックをしません。 authenticate_token (authenticate_xxx) は使えます。


app/models/user.rb

class User < ApplicationRecord

has_secure_password
has_secure_password :token, validations: false
end

irb(main):007:0> u = User.new

=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):008:0> u.valid?
=> false
irb(main):009:0> u.errors.full_messages
=> ["Password can't be blank"]
irb(main):010:0> u.password = u.password_confirmation = 'password'
=> "password"
irb(main):011:0> u.token = 'token'
=> "token"
irb(main):012:0> u.save
(0.3ms) BEGIN
User Create (0.7ms) INSERT INTO "users" ("password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["password_digest", "$2a$10$NC1IH2S8G/RKX7myjNfeeepMknnQUuKIqD60S70iom3ZOQl92j/Cq"], ["token_digest", "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNjr2dibMU/MK"], ["created_at", "2019-05-03 23:42:47.628292"], ["updated_at", "2019-05-03 23:42:47.628292"]]
(7.2ms) COMMIT
=> true
irb(main):013:0> u = User.last
User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 3, name: nil, password_digest: [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">
irb(main):014:0> u.authenticate_token('pass')
=> false
irb(main):015:0> u.authenticate_token('token')
=> #<User id: 3, name: nil, password_digest [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYrQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">"'"]


:validations オプションを省略したときは?

validations: true を指定したときと同じ動作になります。


おまけ

irb の出力で password_digest[FILTERED] となっているのに token_digest[FILTERED] にならないのは、

config/initializers/filter_parameter_logging.rb で設定されていないからです。

token も追加してあげれば [FILTERED] になります。


config/initializers/filter_parameter_logging.rb

# Configure sensitive parameters which will be filtered from the log file.

Rails.application.config.filter_parameters += [:password, :token]


参考情報