deviseにおける過去利用のパスワード制限

### はじめに

Railsのログイン機構のためのgemとして、[`devise`](https://github.com/plataformatec/devise)を利用している場合も多いですが、今回はセキュリティ要件として、過去に利用したパスワードを設定することができないようにするために、[`devise_security_extension`](https://github.com/phatworx/devise_security_extension)を利用して過去に設定したパスワードに対しての制限を行いました。

### deviseの設定

まずは簡単にRailsアプリケーションの作成からdeviseの設定を行います。

#### アプリケーションの作成

```sh
$ rails new devise_app
$ cd devise_app/
```

#### deviseのインストール

Gemfileに以下を追記します。また今回はパスワード再設定のために、再設定メールをブラウザから確認できるように`letter_opener`のgemもインストールします。

```ruby:Gemfile
gem 'devise'
gem 'letter_opener'
```

つづいて`bundle install`の実行を行います。

```shell
$ bundle install
```

#### devise の設定

```sh

$ rails g devise:install

Running via Spring preloader in process 34286
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven\'t yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================
```

deviseに対しての設定情報が表示されるので、表示を参考に必要な設定を行います。
まず`config/environments/development.rb`に以下を追記。

```ruby:config/environments/development.rb
Rails.application.configure do

  # 追記
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  config.action_mailer.delivery_method = :letter_opener

end
```

次に`user`でdevise 用のモデルを作成して、migrate処理を行います。

```shell


$ rails generate devise user

Running via Spring preloader in process 34446
      invoke  active_record
      create    db/migrate/20180422061404_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

$ bundle exec rails db:migrate

== 20180422061404 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0025s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0007s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0010s
== 20180422061404 DeviseCreateUsers: migrated (0.0043s) =======================
```

以上からrailsサーバを立ち上げ`user/sign_in`でログインフォームの画面が表示されればOKです。

<img width="1009" alt="スクリーンショット 2018-04-22 15.27.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/218189/e95682d3-c663-ef40-450e-e16ab89df52e.png">

## devise_security_extension

### devise_security_extensionとは

[`devise_security_extension`](https://github.com/phatworx/devise_security_extension#password-archivable)はdeviseに対してエンタープライズのセキュリティ要件として必要になる拡張機能を提供します。
今回、題材にした過去利用のパスワードの設定制限の他にも、パスワードのexpire機能やcaptcha機能などが利用できるようになります。

### devise_security_extensionの利用

今回は過去のパスワード制限をするための設定を行なっていきます。
なおGithubの[issue](https://github.com/phatworx/devise_security_extension/issues/223)に記載があるように、Rails5.1以上の環境はgem指定のみであるとエラーになってしまうため、以下をのようにGemfileに追記します。

```ruby:Gemfile
gem 'devise_security_extension', git: 'https://github.com/phatworx/devise_security_extension.git'
```

Gemfileへの追記を行なったら`bundle`,`generator`とコマンドを実行します。

```shell
$ bundle install

$ rails generate devise_security_extension:install
Running via Spring preloader in process 34754
      create  config/initializers/devise_security_extension.rb
      create  config/locales/devise.security_extension.en.yml
      create  config/locales/devise.security_extension.de.yml
      create  config/locales/devise.security_extension.it.yml
```

`devise_security_extension`にはdeviseに関しての様々な拡張機能が用意されていますが、今回は過去のパスワードの利用を禁止するために`password_archivable`の機能のみを利用。

 generatorによって取り込んだ`config/initializers/devise_security_extension.rb`ファイルから`password_archivable`に関しての設定のコメントアウトを外します。これによって過去に使用したパスワードのうち5回分(+現在のパスワード)のパスワードの設定を禁止することができます。
用途によって`password_archiving_count`の回数を変更すると良いでしょう。

```ruby:config/initializers/devise_security_extension.rb

Devise.setup do |config|

  # How many passwords to keep in archive
  config.password_archiving_count = 5

  # Deny old password (true, false, count)
  config.deny_old_passwords = true

end
```

次にパスワードの履歴を保存するためのマイグレーションファイルの作成を行います。
マイグレーションファイルには[公式のREADME](https://github.com/phatworx/devise_security_extension#password-archivable)に従って定義を行えばOKです。

```
$ rails g migration create_old_passwords
Running via Spring preloader in process 34823
      invoke  active_record
      create    db/migrate/20180422065429_create_old_passwords.rb
```

```ruby:db/migrate/20180422065429_create_old_passwords.rb
class CreateOldPasswords < ActiveRecord::Migration[5.2]
  def change
    create_table :old_passwords do |t| 
      t.string :encrypted_password, :null => false
      t.string :password_archivable_type, :null => false
      t.integer :password_archivable_id, :null => false
      t.datetime :created_at
    end 
    add_index :old_passwords, [:password_archivable_type, :password_archivable_id], :name => :index_password_archivable
  end 
end
```

マイグレーションファイルを作成したらDBに対して適用を行う。

```shell
$ bundle exec rails db:migrate

== 20180422065429 CreateOldPasswords: migrating ===============================
-- create_table(:old_passwords)
   -> 0.0024s
-- add_index(:old_passwords, [:password_archivable_type, :password_archivable_id], {:name=>:index_password_archivable})
   -> 0.0006s
== 20180422065429 CreateOldPasswords: migrated (0.0033s) ======================
```

最後に`app/model/user.rb`テーブルに対して`:password_archivable`の設定を行います。

```ruby:app/model/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable

  # :password_archivableを追記
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :password_archivable
end
```

これで設定は終了なので、サーバを再起動して確認。
過去5回に設定したことのあるパスワードを設定しようとすると、以下のようなエラーメッセージが表示されます。

<img width="1127" alt="スクリーンショット 2018-04-22 16.24.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/218189/13b50ed1-9678-f768-e07b-84ac1eb1903a.png">

あとは日本語に対する`locale`ファイルを作成すれば、日本語環境においても問題なく動作させることができます。

###  最後に

今回に`devise_security_extension`のgemを利用して、deviseに対して過去に設定したパスワードに対しての制限を行いました。
`devise_security_extension`では他にも、さまざな拡張機能をdeviseに対して設定できるので、機会があれば利用したいと思います。