Rails deviseによるユーザー認証 メールによる認証、emailとusernameのどちらでもログイン可能にするまで

# 概要
本記事では、Railsのgem「devise」を用いてユーザーログイン機能を実装していきます。
ステップにそって作業を進めれば、実装出来ます。

##本記事でやること
* 基本的なサインイン・ログイン・ログアウト
* 確認メール付き認証
* emailとusernameのどちらでもログイン可能にする

#実装
##deviseの導入
下記をGemfileに記述してbundle install。

```rb:Gemfile
gem 'devise'
```

その後、以下のコマンドでdeviseの設定ファイルの生成。

```
$ bundle exec rails g devise:install
```

以下のようにメッセージが表示されるのではじめのステップではこちらを参考に作業を進めます。

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

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. If you are deploying on Heroku with Rails 3.2 only, you may want to set:

       config.assets.initialize_on_precompile = false

     On config/application.rb forcing your application to not access the DB
     or load models when precompiling your assets.

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

       rails g devise:views

===================================================================
```
##各種設定
### default_url_optionsの設定
下記を追記。

```rb:config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
```

### rootパスの設定
下記のようにrootパスを設定。ここは各々rootパスに設定したいものを設定。

```rb:config/routes.rb
root "home#index"
```

### flashメッセージの設定
下記を追記。ひとまず、```<body>```の直下でいいかと。

```erb:app/views/layouts/application.html.erb
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>
```

### deviseのviewの生成
下記のコマンドでdeviseのviewの出力。

```
$ bundle exec rails g devise:views
```

##Model、migrationファイルの作成

以下のコマンドでuserモデル及びマイグレーションファイルが作成されます。

```
$ bundle exec rails g devise user
```

### 余談

ちなみに、今回はUserモデルで作業を進めますが、

```
$ bundle exec rails g devise member
```
のようにモデル名は変更出来ます。このときはdeviseのメソッド名もモデル名に合わせて変わる点には注意が必要です。
例えば、```user_signed_in?```メソッドは```member_signed_in?```となります。


### 認証に必要なカラムを追加する
デフォルトでは以下のファイルの22行目あたりがコメントアウトされているので以下の項目のコメントアウトをすべてはずす。メール認証のためのカラムで今回は使用します。

```rb:db/migrate/xxxxxxxxxxxx_devise_create_users.rb
  ## Confirmable
  t.string   :confirmation_token
  t.datetime :confirmed_at
  t.datetime :confirmation_sent_at
  t.string   :unconfirmed_email # Only if using reconfirmable
```

そして、migrate。

```
$ bundle exec rake db:migrate
```

## deviseの各種設定
### ログアウトボタンを設置する
ログアウトボタンをとりあえず作っておきましょう。（ログインのテストをするときにないと困るので）
先ほどのフラッシュメッセージの直下に下記を記述しておきましょう。
deviseがログアウトの処理は準備してくれているので、それを使用するだけです。
パスについては```rake routes```で確認出来ます。
また、```user_signed_in?```というユーザーがログインしているかどうかを判別するメソッドを使用することで、ログイン時のみログアウトボタンを出力します。

```erb:app/views/layouts/application.html.erb
  <% if user_signed_in? %>
    <p><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></p>
  <% end %>
```

### ログインしていないときにログイン画面にリダイレクトをかける

下記を追加しましょう。```:authenticate_user!```によってログインしていないときログイン画面にリダイレクトをかけることが出来るようになります。ちなみに、ここではapplication_controller.rb内で使用しましたが、各コントロラー単位でも使用出来るので必要があれば書き換えて下さい。

```rb:app/controllers/application_controller.rb
before_action :authenticate_user!
```

## メールに必要な設定を行う
### メール認証のモジュールを読み込む
user.rbの4行目付近に```:confirmable```を読み込む。

```rb:app/model/user.rb
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable, :confirmable
```

### 追記 2019.01.23

devise 4.5.0から `:trackable` はデフォルトではなくなったようです。`:trackable`を追加する必要はありません。

### SMTPサーバーの設定
メールを実際に送信するためには、SMTPサーバーを設定しなければなりません。
ここでは、gmailを用いてメールの送信を行います。
**以降でgmailを使用するのでメールアドレスとパスワードが必要になります**

2段階認証をかけている場合は解除しなければ、使用できません。


```rb:config/environments/development.rb
config.action_mailer.smtp_settings = {
  :enable_starttls_auto => true,
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => 'smtp.gmail.com',
  :user_name => "xxxxxxx@gmail.com", #gmailアドレス
  :password => "xxxxxxxxxxx", #gmailパスワード
  :authentication => 'login',
}
```

github等にアップロードしてソースコードを公開される場合は注意が必要です。自身のgmail
のアドレスとパスワードを公開することになるので勝手に使用されるおそれがあります。
その場合は、gemの「config」を使うことをおすすめします。
下記の記事の中で使用法について記述してあるので参考までに。

[rails 一度gitHubにあげたID・パスワード等を履歴から消し、環境ごとに管理する](http://qiita.com/shizuma/items/efcbd2593446015130a6)

## ここまでの確認をする
ここまで出来れば、メール認証つきのログイン認証の出来上がりです。
実際にサインイン・ログイン・ログアウトの一連の処理を試してみてください。
まだ、ここまででは通常のemailでの認証です。

SMTPにエラーが発生するときは「安全性の低いアプリのアクセス」をonにしていない可能性があります。
メールが届くと思うのでそれに沿って設定して下さい。
一応、下記のURLから設定出来るのでこちらからでも設定変更可能です。
https://www.google.com/settings/security/lesssecureapps 

それでは、次からemailとusernameのどちらでもログイン可能にしていきます。

## userテーブルにusernameカラムを追加する

migrationファイルの作成。

```
$ bundle exec rails g migration AddUserNameToUsers username:string:uniq
```
その後、

```
$ bundle exec rake db:migrate
```

## ストロングパラメーターの設定

deviseのコントローラーはgemの中にあるため、直接変更出来ません。そのため、新たにカラムを追加したときは別途ストロングパラメーターの設定をする必要があります。
今回は:usernameをカラムとして追加し、さらにこの後に:loginをフォームの中で使用していくのでこちらの設定が必要となります。

```rb:app/controllers/application_controller.rb
# 以下を追加
  before_filter :configure_permitted_parameters, if: :devise_controller?

  protected
    def configure_permitted_parameters
      devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
      devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login, :username, :email, :password, :remember_me) }
      devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) }
    end
```

### 追記 2017.2.27

Rails5の変更に伴いdeviseのバージョンがあがりストロングパラメータの追加の仕方が変わっている可能性があります。devise4をお使いの方は以下の記事を参照して下さい。

[deviseでストロングパラメータを追加する (Rails5を受けての変更点)](http://qiita.com/shizuma/items/b176f165a7c8b1049eae)

## ログイン用のパラメーターの設定
emailとusernameのどちらでもログイン出来るようにするためのパラメーター:loginを設定します。

### loginパラメーターのアクセサを設定
カラムに存在しないフィールドをフォーム内で設定すると通常では利用することが出来ません。そのため、```attr_accessor```を定義することで利用可能にします。

```rb:app/models/user.rb
  attr_accessor :login
```

### ログイン認証用のキーをloginに設定

先ほど、confirmableを追記したところにさらに```:authentication_keys => [:login]```を追加します。


```rb:app/models/user.rb
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, 
         :validatable, :confirmable, :authentication_keys => [:login]
```

### ログイン認証の条件をオーバーライド

以下を追記します。ここではdeviseのログイン認証のメソッドをオーバーライド（上書き)することで新たなログイン条件に設定しています。
ここでは、usernameとemailのどちらでもログイン出来るように条件を設定しています。

```rb:app/models/user.rb
# 以下を追加
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if login = conditions.delete(:login)
      where(conditions).where(["username = :value OR lower(email) = lower(:value)", { :value => login }]).first
    else
      where(conditions).first
    end
  end
```

### 追記 2019.01.23

@takotakot さんより

> lower(email) = lower(:value)" のところ、パフォーマンスを考えると気になります。
1. DB に保存するときに lower して保存する
2. lower してあるカラムを追加する
3. 何も与えないで比較させる
等工夫した方が良さそうです。full scan になってしまうので。

### usernameのバリデーションをかける
usernameのバリデーションがない状態なので最低限のバリデーションはつけておきましょう。
下記を追加することでusernameがuniqueであることと、文字数が4から20文字であることを設定出来ます。(case_sensitive: :falssは大文字小文字の区別をしないということ）
こちらは各自お好みの設定をすればオーケーです。

```rb:app/models/user.rb
# 以下を追加
validates :username,
  uniqueness: { case_sensitive: :false },
  length: { minimum: 4, maximum: 20 }
```

半角英数字のバリデーションをつけるのも良さそうですね。

```rb
format: { with: /\A[a-z0-9]+\z/, message: "ユーザー名は半角英数字です"}
```


### 追記 2019.01.25

@takotakot さんより

> `attr_accessor :login`を予期するということは、フォーム側で、name や email ではなく、login として情報送信を試みるはずなのですが、その部分の説明が抜け落ちています。
参考にされている記事の「Viewファイルの書き換え」というところに書いてあります。

もし、View周りでお困りの場合はこちらもご参照ください。[Rails4.1でdeviseのログインIDをemailとusernameのどちらでも可能にする](http://qiita.com/HamaTech/items/8a4694fa40414aa029ea)

# 参考
[Rails4.1でdeviseのログインIDをemailとusernameのどちらでも可能にする](http://qiita.com/HamaTech/items/8a4694fa40414aa029ea)
後半部分はこちらを参考にしています。