Railsアプリでセキュアなパスワードを使う

Railsアプリでセキュアなパスワードを使用する方法 +α 。


準備

gem追加

Gemfileを修正し、bundle install します。
Railsアプリを rails new で作成後に特に消していなければ、Blowfish暗号を利用できる bcrypt というgemがコメントアウトされている状態になっているはずなので、これをアンコメントします。

## 省略 ##

- # gem 'bcrypt', '~> 3.1.7'
+ gem 'bcrypt', '~> 3.1.7'

## 省略 ##
$ bundle install

実装

セキュアなパスワードは has_secure_password というメソッドを、生成されるmodelで呼び出します。
このメソッドを使うためには、モデル内に password_digest という属性が含まれている必要があるため注意します。

モデル作成

$ rails g model User name:string email:string password_digest:string

modelファイル編集

app/models/user.rb に下記を追加します。

class User < ApplicationRecord

before_save { email.downcase! }

VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i.freeze
# 半角英数字大文字小文字をそれぞれ1文字以上含む
VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\d)[a-zA-Z\d]{8,16}+\z/.freeze

validates :name,
presence: true,
length: { in: 1..30 }

validates :email,
presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }

has_secure_password
validates :password,
presence: true,
length: { minimum: 8 },
format: { with: VALID_PASSWORD_REGEX }
end

Migration

$ rails db:migrate
invoke active_record
create db/migrate/20190829085116_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml

確認

User.new で作成したオブジェクトを save メソッドで保存。
ハッシュ化されたパスワードが登録されていることを確認します。

$ rails c
Loading development environment (Rails 5.2.3)

irb(main):001:0> user = User.new(name: "kohbis", email: "tEst@test.com", password: "Passw0rd")
=> #<User id: nil, name: "kohbis", email: "tEst@test.com", password_digest: "$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.1...", created_at: nil, updated_at: nil>

irb(main):002:0> user.save
(0.9ms) BEGIN
User Exists (1.3ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = 'tEst@test.com' LIMIT 1
User Create (0.5ms) INSERT INTO `users` (`name`, `email`, `password_digest`, `created_at`, `updated_at`) VALUES ('kohbis', 'test@test.com', '$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.18p.sIGQ/Ce', '2019-08-29 12:39:14', '2019-08-29 12:39:14')
(2.2ms) COMMIT
=> true

irb(main):003:0> User.all
User Load (0.9ms) SELECT `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [#<User id: 1, name: "kohbis", email: "test@test.com", password_digest: "$2a$12$pPsDOoJErpVXsdtLWNoGvOTdmUFBSGk/nf492aA0N.1...", created_at: "2019-08-29 12:39:14", updated_at: "2019-08-29 12:39:14">]>

最後にvalidationが効いているかも確認しておきます。

$ rails c
Loading development environment (Rails 5.2.3)
irb(main):001:0> user = User.new(name: "", email: "@not.address", password: "password")
(0.6ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, name: "", email: "@not.address", password_digest: "$2a$12$5PovSBWfHCPDOKQcD3lCAuOC7YepZvIwIaocF01tEI5...", created_at: nil, updated_at: nil>

irb(main):002:0> user.valid?
User Exists (0.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = '@not.address' LIMIT 1
=> false

irb(main):003:0> user.errors.messages
=> {:name=>["can't be blank", "is too short (minimum is 1 character)"], :email=>["is invalid"], :password=>["is invalid"]}

以上。

← Blog