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

# はじめに

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

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

# 環境

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

- Ruby: 2.4.1
- Rails: 5.0.7
- bcrypt: 3.1.7

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

# 導入方法

## テーブル設計

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

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

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

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

```ruby: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`テーブルを作成します。

```bash
$ rails db:migrate
```

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

```sql

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 を記述します**

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

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

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

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

## `bcrypt`の`Gem`を追加

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

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

```ruby:Gemfile
...

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

...
```

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

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

`User`インスタンスを作成

```console
¥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>
```

保存します

```bash
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 
```

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

```bash
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`も確認しておきます。

```bash
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メソッドは、パスワードを認証するためのメソッドです。試しに使って見ます。

```ruby
@angou.authenticate("test")
```

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

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

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

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

---

以上です！

# この記事を読んだ方に

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



# 参考
- [Railsチュートリアル / 暗号化されたパスワード](https://railstutorial.jp/chapters/modeling-users?version=4.0#sec-an_encrypted_password)
- https://qiita.com/tatane616/items/c00182179e498aa9c53e
- https://qiita.com/chobi9999/items/20b962a324a0bdbfc0dc
- https://qiita.com/hmuronaka/items/f2107e8ad84d842ff476
- https://codezine.jp/article/detail/10014
- http://rice-american.github.io/blog/2014/03/03/secdb-rails/