deviseによるgmail認証、本番環境への適用

概要
---
本記事では、Ruby on RailsのGem「devise」を用いて、ユーザー新規登録機能を実装していきます。
本番環境でメール認証が適用できない点で詰まったので、本番環境での設定方法まで解説していきます。

本記事でやること
---
- Gmail認証を使ったユーザー新規登録方法
- Gmail側の設定
- メール内容をカスタマイズ
- 本番環境でもGmail認証が使えるようにする

環境
---
- バックエンド
 - Ruby 2.6.5
 - Rails 6.0.3

- Gem
 - devise
 - devise-i18n
 - dotenv-rails

- データベース
 - MySQL 5.7

参考にした記事
---
[【Rails備忘録】deviseまとめ](https://qiita.com/ShinyaKato/items/a098a741a142616a753e)

[Deviseでログイン機能を追加・日本語化・Bootstrap4適用まで
](https://qiita.com/take18k_tech/items/a36d77316e32a6696205)

[deviseを使ったログイン機能を実装する！メール認証機能付き](https://qiita.com/kskumgk63/items/aa581b6b3f8c66fa82e2)

[【Rails】deviseでURL認証付きのメールを送信してみる](https://qiita.com/ozackiee/items/21fcad4a1564136b9510)

完成した後の動作
---
①ユーザー新規登録画面で、情報を入力
![sign_up_page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/9ab99a21-4197-300f-f5c6-28f9496685a8.png)

②入力したメールアドレス宛にメールが送信されるフラッシュが表示
![sing_up_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/775880b5-4dfc-7669-b93f-b96ad1efe0fc.png)


③Gmailを確認して、メールが届いていることを確認、メール内リンクをクリック
![sign_up_gmail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/42f083ae-ac31-1bed-9705-8ccdcd448b4e.png)

④メール認証が完了し、ログインが可能となる
![sign_up_aleady_auth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/6e3e8ba8-f3cf-211e-88f0-2f69454c7a62.png)

***補足***
メール内のリンクをクリックしないと本人確認をするフラッシュが表示され、ログインができない仕様
![sing_up_authentication.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/bb231bda-cd4d-e192-f277-fcbd03a46ea6.png)

アカウント確認メールを再送することも可能（本記事に実装方法記載していません）
![sign_up_mail_resend.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/d8301140-00b0-dd2c-29ca-695248dfaa43.png)


前提
---
- RailsのMVCなど基本を理解されていること
- Railsアプリケーションを新規作成済みであること
- deviseがインストール済みであること
- Gmailアカウントを作成済みであること

実装(準備編)
---
マイグレーションを以下の様な構成にして、マイグレーションを実行してください

***補足***
- メール認証のみであれば、Confirmableの箇所のみコメントアウトすれば良いが、パスワードリセットやログイン情報保持をする場合は、以下のようにRecoverable、Rememberableの箇所もコメントアウトしてください。

``` ruby:20200627035125_devise_create_users.rb
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      t.string   :unlock_token # Only if unlock strategy is :email or :both
      t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token,   unique: true
    add_index :users, :unlock_token,         unique: true
  end
end
```

deviseの機能が使えるようuser.rbに以下を記載

```ruby:app/models/user.rb
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :validatable,
          :confirmable, :lockable, :timeoutable, :trackable
```

新規登録時のエラーメッセージなどを日本語化させましょう
以下の様な構成になります

``` ruby:config/locales/devise.views.ja.yml 
ja:
  activerecord:
    attributes:
      user:
       ~~省略~~
  devise:
    confirmations:
      confirmed: メールアドレスが確認できました。
      new:
        resend_confirmation_instructions: アカウント確認メール再送
      send_instructions: アカウントの有効化について数分以内にメールでご連絡します。
      send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。
　　　　　　　　　　　 ~~省略~~
    mailer:
      confirmation_instructions:
        action: メールアドレスの確認
        greeting: "%{recipient}様"
        instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。
        subject: "アカウントの有効化のご案内"
　　　　　　　　　　　　　~~省略~~

    shared:
      links:
        back: 戻る
        didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか?
        didn_t_receive_unlock_instructions: アカウントの凍結解除方法のメールを受け取っていませんか?
        forgot_your_password: パスワードを忘れましたか?
        sign_in: ログイン
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: 新規登録
      minimum_password_length: "（6~20桁の半角英数字）"
　　　　　　　　　　　　　~~省略~~
```

Gmail側の設定をします。
①Gmailにログインし、２段階認証はオンにしてください
![2段階認証.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/58ba5328-3562-ca3d-ac0b-6f8e3207dbe3.png)

②アプリパスワードの生成
Gmail →　設定 → アカウント → セキュリティ → アプリパスワード → パスワードを入力
→　アプリを「その他」に設定し、「現在作成しているアプリケーション名」　→ 生成
→　以下の画面になると思いますので、必ずメモしてください！（アプリパスワードは再生成できません）
![アプリパスワード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565859/b3218c9b-93d9-64fc-afd1-321b374f6c50.png)


Gmailでメールを受け取れるようにする設定にします(開発環境)

``` ruby:config/enviroments/development.rb
  config.action_mailer.default_url_options = {  host: 'localhost', port: 3000 }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address:"smtp.gmail.com",
    domain: 'gmail.com',
    port:587,
    user_name: Rails.application.credentials.gmail[:user_name],
    password: Rails.application.credentials.gmail[:password],
    authentication: :login
  }
```

***重要***
GitHubにuser_nameとpasswordを記載するのは危険なため、credentialで保存してください!
以下のコマンドを実行

``` ruby:terminal
EDITOR=vim rails credentials:edit
```
INSERTにして、以下を記入したら:wqで抜ける

```ruby:vim
gmail:
 user_name: Gmailアドレス
 password: 先ほど生成したアプリパスワード
```

ここまできたら一度Githubにプッシュしましょう


実装(Mailer編)
---
デフォルトのMailer設定を変更,送信元メールアドレスを変更
以下の様な構成にします。

***補足***
config.confirm_withinでURLの有効期限が設定できます

``` ruby:config/initializers/devise.rb
Devise.setup do |config|
  config.mailer = 'Users::Mailer'

  config.mailer_sender = '認証メール<〇〇〇〇〇〇@gmail.com>'

  require 'devise/orm/active_record'

  config.reconfirmable = true

  config.expire_all_remember_me_on_sign_out = true

  config.password_length = 6..20

  config.timeout_in = 1.day

  config.confirm_within = 1.days

  config.reset_password_within = 6.hours

  config.scoped_views = true

  config.sign_out_via = :delete
end
```

Mailer自体をオーバーライド

```ruby:app/mailers/users/mailer.rb 
class Users::Mailer < Devise::Mailer
  helper :application
  include Devise::Controllers::UrlHelpers
  default template_path: 'devise/mailer'

  def confirmation_instructions(record, token, opts={})
    if record.unconfirmed_email != nil
      opts[:subject] = "認証を行ってメールアドレス変更手続きを完了してください"
    else
      opts[:subject] = "認証を行ってユーザ登録を完了してください"
    end
    super
  end
end
```

実装(MVC作成編)
---
Viewは以下の様な構成にします。

補足
- if文をフォームの下に設置することにより、バリデーションエラーが以下に表示される様になります。
- cssクラスはご自身で設定されているものに変更してください。

``` ruby:app/views/devise/registrations/new.html.erb 
<section class="login">
  <div class="login__imgBx">
    <%= image_tag 'introduction.jpg' %>
  </div>
  <div class="login__contentBx">
    <div class="login__formBx">
      <div class="login__title">新規登録</div>
      <%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %>

      <div class="login__inputBx">
        <%= f.label :name, {class: 'login__label'} %>
        <%= f.text_field :name, maxlength: '50' %>
        <% if @user.errors.include?(:name) %>
          <p class="login__form-error"><%= @user.errors.full_messages_for(:name).first %>
        <% end %>
      </div>

      <div class="login__inputBx">
        <%= f.label :email, {class: 'login__label'} %>
        <%= f.email_field :email, maxlength: '100 '%>
        <% if @user.errors.include?(:email) %>
          <p class="login__form-error"><%= @user.errors.full_messages_for(:email).first %>
        <% end %>
      </div>

      <div class="login__inputBx">
        <%= f.label :password, {class: 'login__label'} %>
        <%= f.password_field :password,
                              autocomplete: "off",
                              minlength: @minimum_password_length,
                              maxlength: '20'
        %>
        <% if @minimum_password_length %>
          <small class="form-text text-muted"><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></small>
        <% end %>

        <% if @user.errors.include?(:password) %>
          <p class="login__form-error"><%= @user.errors.full_messages_for(:password).first %>
        <% end %>
      </div>

      <div class="login__inputBx">
        <%= f.label :password_confirmation, {class: 'login__label'} %>
        <%= f.password_field :password_confirmation,
                              autocomplete: "off",
                              minlength: @minimum_password_length,
                              maxlength: '20'
        %>
        <% if @user.errors.include?(:password_confirmation) %>
          <p class="login__form-error"><%= @user.errors.full_messages_for(:password_confirmation).first %>
        <% end %>
      </div>

      <div class="login__inputBx">
        <%= f.submit "新規登録" %>
      </div>
    <% end %>
    <%= render 'devise/shared/links' %>
    </div>
  </div>
</section>
```

controllerは以下の様な構成にします。(newとcreateアクションをオーバーライドします。)

``` ruby:app/controllers/users/registrations_controller.rb 
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController

  def new
    super
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:notice] = "ユーザー認証メールを送信いたしました。認証が完了しましたらログインをお願いいたします。"
      redirect_to new_user_session_path
    else
      flash[:alert] = "ユーザー登録に失敗しました。"
      render action: :new and return
    end
  end

  private
    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    end
```

メール内容のカスタマイズ
app/views/devise/mailer/confirmation_instructions.html.erb を作成
作成後以下の様な構成にします。

***補足***
登録済みの方が、再度アカウント確認をした場合を想定して、条件分岐で内容を切り替えています。

```ruby:app/views/devise/mailer/confirmation_instructions.html.erb 
  <div><%= @user.name %>様</div>
  <div>〇〇〇〇〇〇〇をご利用いただきありがとうございます。</div>

  <% if @user.try(:unconfirmed_email?) %>
    <div>以下のリンクからユーザの認証を行ってメールアドレス変更手続きを完了してください。</div>
    <div><%= link_to 'ユーザ認証を実行する', confirmation_url(@user,confirmation_token: @token) %></div>
  <% else %>
    <% if @user.confirmed_at != nil %>
      <div>会員登録が完了済みです。</div>
      <div>下記リンクからログインをお願いいたします。</div>
      <p>http://localhost:3000/</p>
    <% else %>
      <div>以下のリンクからユーザの認証を行ってユーザ登録を完了してください。</div>
      <div><%= link_to 'ユーザ認証を実行する', confirmation_url(@user,confirmation_token: @token) %></div>
      <div> このURLの有効期限は24時間です。</div>
    <% end %>
  <% end %>
```

ここで開発環境でGmailに認証メールが届くか確認しましょう。
確認できたらGithubにプッシュしましょう

実装(本番環境編)
---
production.rbに認証メール送信で必要なことがあります。
以下の様な構成になります。

***補足***
ドメイン名を設定してあげないと、本番環境ではGmailに認証メールが届かないため設定しましょう。
環境変数でGmailアドレスやアプリパスワードを渡す必要があるため、dotenv-rails Gemをインストールし、環境変数を.envファイルに記載しましょう

```ruby:config/environments/production.rb
Rails.application.configure do
  ~~省略~~

  config.action_mailer.perform_caching = false

  config.action_mailer.default_url_options = { host: 'アプリケーションのドメイン名'}

  config.action_mailer.perform_deliveries = true

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address:"smtp.gmail.com",
    domain: 'gmail.com',
    port:587,
    user_name: ENV['SEND_MAIL'],
    password: ENV['GMAIL_SPECIFIC_PASSWORD'],
    authentication: :login,
    openssl_verify_mode: 'none',
    enable_starttls_auto: true
  }

  config.action_mailer.perform_caching = false

  config.action_mailer.raise_delivery_errors = true
  
  ~~省略~~

end

```
環境変数の設定は以下の様になります。

```ruby:.env
SEND_MAIL="Gmailアドレス"
GMAIL_SPECIFIC_PASSWORD="生成したアプリパスワード"
```

環境変数はGithubにあげるのは危険なため、.gitignoreファイルに.envを追加しましょう

```ruby:.gitignore
 ~~省略~~
.env

```
Githubにプッシュしします
これで、本番環境でメール認証が飛んでいるはずです。


苦労した点
---
Gmailアドレスとアプリパスワードの設定することに気づかず、詰まりました。

本番環境でメールが飛ばない事態が発生しており、本番環境用のMailer設定については記事が少なく大変でしたが、設定できました。

まとめ
---
deviseはブラックボックスなところが多く、大変ですが、Qiitaを参考に実際に手を動かして、試行錯誤すると、便利な部分はデフォルトでカスタマイズしたいところはカスタマイズできるのでこちらの記事を参考に皆さんのポートフォリオ作りの糧になればと思います。

ご質問などがありましたらコメントいただけると幸いです。