生のパスワードをデータベースに保存すると、セキュリティ上問題になる。
そこで、bcryptジェムとhas_secure_passwordメソッドを用いて、パスワードをハッシュ化して保存する。
ハッシュ化とは、Rubyのハッシュのことではなく、文字列をランダムな文字列に変換することである。
暗号化のようであるが、復元できないという点が違うらしい。
bcryptジェム
Gemfileにbcryptを追加する。
gem 'bcrypt', '3.1.12'
bundle installしておく。
has_secure_passwordメソッドとpassword_digestカラム
Userモデルにhas_secure_passwordメソッドを追加する。
class User < ApplicationRecord
.
.
.
has_secure_password
end
このメソッドを使用するためには、Userモデルにpassword_digestカラムが必要になる。
カラム追加用のマイグレーションファイルを作成する。
$ rails generate migration add_password_digest_to_users password_digest:string
class AddPasswordDigestToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :password_digest, :string
end
end
マイグレーションファイルの末尾をto_usersとし、カラム名と型を入れておくと、自動でadd_columnを追加してくれる。
$ rails db:migrateしておく。
テストの修正
この時点で、テストが失敗する。
has_secure_passwordには、仮想的な属性としてpasswordとpassword_confirmationが追加されており、これらにはバリデーションが設定されている。
Userモデル用のテストでは、setupでこれらの属性の値を指定していないため、テストが失敗する。
そこで、これを修正しておく。
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
テストをして、成功することを確認する。
パスワードの最小文字数
パスワードは必ず存在していなければならない。
また、パスワードは短すぎてもよくない。
そこで、存在性の検証と、最小文字数を制限するバリデーションを追加する。
まずはこれらについてテストを書く。
test "password should be present (nonblank)" do
@user.password = @user.password_confirmation = " " * 6
assert_not @user.valid?
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
最小文字数は6文字とし、空白は無効である。
ここでは、多重代入を使って、@user.passwordと@user.password_confirmationの二つに、同時に文字列を代入している。
Userモデルのpasswordカラムに、存在性と文字数制限のバリデーションを追加する。
class User < ApplicationRecord
.
.
.
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
end
テストをして、成功することを確認する。
なお、password_confirmationに対するバリデーションは不要のようである。
authenticateメソッド
has_secure_passwordメソッドをUserモデルに適応したことで、Userオブジェクト(インスタンス)に対してauthenticateメソッドを使用できるようになる。
password属性(カラム)に代入された生のパスワードは、bcryptによってハッシュ化され、password_digestに保存される。
authenticateメソッドは、生のパスワードを引数に取り、それをハッシュ化して、password_digestに保存されているハッシュ値と比較する。
比較した結果が正しければ、このメソッドは該当するUserオブジェクトを返す。
>> user.authenticate("foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46",
password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">
ログイン機能の実装には、このUserオブジェクトの論理値がtrueであることを利用するので、これに!!(bang bang)を使用して論理値に変換する。
>> !!user.authenticate("foobar")
=> true