Ruby
Rails
Gem

Rails のモデル(フォーム)でパスワードを暗号化して保存する方法

はじめに

フォームを介してユーザが入力したパスワードをそのままDBに保存するのは危険なので、暗号化しますよね。パスワードを適切に暗号化することで、第三者に、DBからパスワードをコピー盗まれないようにできます。

ということで、今日は、Rails のモデル(フォーム)でパスワードを暗号化して保存する方法を紹介します。

環境

この記事では以下の環境(2018年6月12日時点)で動作確認できました。

  • Ruby: 2.4.1
  • Rails: 5.0.7
  • bcrypt: 3.1.7

bcryptは 今回使用するGemです。

導入方法

テーブル設計

今回は仮で、よくありそうな、ユーザー情報を登録するUserモデルを作成していきましょう。

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

ポイント1:モデルにpassword_digest属性を追加する

このコマンドで生成されるマイグレーションファルは、以下の感じです。

20180613094857_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password_digest

      t.timestamps
    end
  end
end

マイグレーションを実行することで、usersテーブルを作成します。

$ rails db:migrate

これで、usersテーブルが作成されました。

mysql> describe users;
+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| id              | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name            | varchar(255) | YES  |     | NULL    |                |
| email           | varchar(255) | YES  |     | NULL    |                |
| password_digest | varchar(255) | YES  |     | NULL    |                |
| created_at      | datetime     | NO   |     | NULL    |                |
| updated_at      | datetime     | NO   |     | NULL    |                |
+-----------------+--------------+------+-----+---------+----------------+

できたカラムは

  • ユーザの名前:email:string
  • ユーザのメールアドレス:email:string
  • ログイン認証のためのユーザパスワード(暗号化される):password_digest:string

です。

password_digestとは

暗号の分野では、暗号化(ダイジェスト)された文字列をメッセージダイジェストと呼びます。メッセージダイジェストは、元の文字列に戻すことができません。

http://wa3.i-3-i.info/word15954.html

has_secure_passwordではデータベース内のpassword_digestという属性に保存できるようになります。(passwordではない点に注意してください)

モデル

ポイント2:$ rails g model ...で生成されたモデルファイルにhas_secure_password を記述します

app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end

こうするだけで、下記の暗号化に関する便利な機能が提供されます。

  • users テーブルにパスワードを保存するとき、パスワードを暗号化して保存してくれる
  • ログイン用のフォーム(View)にpasswordpassword_confirmationという変数をモデルに追加すれば暗号化してDBに保存してくれる
  • ログイン認証用のメソッドであるauthenticateが使用できる

簡単に言えば、password_digestカラムを用意して、モデルファイルにhas_secure_passwordを記述すれば、ログインの機能をよしなにやってくれるやつです。

bcryptGemを追加

暗号化のためにはbcryptというGemが必要です。

ポイント3:現在、Gemfileでは、bcryptがコメントアウトされているので外しましょう

Gemfile
...

# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

...

Gemfileが更新できたので、$ bundle installしてbcryptをインストールします。bundle installした後は、サーバを再起動しないとGemがうまく読み込まれないのでやっておきましょう。

rails コンソールでユーザーを作成

Userインスタンスを作成

¥2.4.1 :001 > user = User.new(name: 'taro', email: 'taro@gmail.com', password: 'taro')
   (3.1ms)  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: "taro", email: "taro@gmail.com", password_digest: "$2a$10$1gpw0o4ZTEUoTfk3bOmmJeMKqiz2HPpZ4jACHJfsDw5...", created_at: nil, updated_at: nil>

保存します

2.4.1 :002 > user.save
   (0.1ms)  BEGIN
  User Exists (0.3ms)  SELECT  1 AS one FROM `users` WHERE `users`.`email` = 'taro@gmail.com' LIMIT 1
  User Create (0.2ms)  INSERT INTO `users` (`name`, `email`, `password_digest`, `created_at`, `updated_at`) VALUES ('taro', 'taro@gmail.com', '$2a$10$1gpw0o4ZTEUoTfk3bOmmJeMKqiz2HPpZ4jACHJfsDw5Ad839Tp.xu', '2018-06-13 14:20:55', '2018-06-13 14:20:55')
   (1.7ms)  COMMIT
 => true 

保存できました。確認して見ましょう。

2.4.1 :003 > User.last
  User Load (0.3ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
 => #<User id: 1, name: "taro", email: "taro@gmail.com", password_digest: "$2a$10$1gpw0o4ZTEUoTfk3bOmmJeMKqiz2HPpZ4jACHJfsDw5...", created_at: "2018-06-13 14:20:55", updated_at: "2018-06-13 14:20:55"> 

最後に保存したものを出す方法で、さっき保存したものが出力されました。

password_digestカラムに入っているデータがダイジェスト(暗号化)されているとわかります。

has_secure_passwordによって提供されるログイン認証メソッドであるauthenticateも確認しておきます。

2.4.1 :004 > user = User.last
  User Load (0.4ms)  SELECT  `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
 => #<User id: 1, name: "taro", email: "taro@gmail.com", password_digest: "$2a$10$1gpw0o4ZTEUoTfk3bOmmJeMKqiz2HPpZ4jACHJfsDw5...", created_at: "2018-06-13 14:20:55", updated_at: "2018-06-13 14:20:55"> 
2.4.1 :005 > user.authenticate('tarachang')
 => false 
2.4.1 :006 > user.authenticate('taro')
 => #<User id: 1, name: "taro", email: "taro@gmail.com", password_digest: "$2a$10$1gpw0o4ZTEUoTfk3bOmmJeMKqiz2HPpZ4jACHJfsDw5...", created_at: "2018-06-13 14:20:55", updated_at: "2018-06-13 14:20:55"> 

正しいパスワード を入力すると、該当するユーザーのインスタンスが返されました。

authenticateメソッドとは

authenticateメソッドは、パスワードを認証するためのメソッドです。試しに使って見ます。

@angou.authenticate("test")

もし、@angou(モデルのオブジェクト)のパスワードがtestならtrueが返ってきます。

暗号化の実装手順はこんな感じです!最後におさらい!

暗号化のポイントのおさらい

  • モデル(マイグレーションファイル)に追加するカラム名はpassword_digestとする
  • has_secure_passwordをモデルファイルに記述する
  • bcryptというgemをインストールする

以上です!

この記事を読んだ方に

この記事を読んで、誤っている箇所をみつけたり、追記した方がいい内容などありましたら、編集リクエストやコメント欄で指摘していただけると助かります。

参考