devise_token_authインストール後、deviseのconfirmableでメール認証機能を追加実装する




# はじめに
`devise_token_auth`をインストールしている状態で、後から`devise`のメール認証機能を追加実装してみたところ、
詰まったポイントがあったので認証APIの実装方法と一緒に備忘録として残します。

**※大前提として、今回詰まったことは`devise_token_auth`をインストールする前に`rails g devise:install`しておけば発生しない問題でした。。。
最初から実装する場合は
`rails g devise:install` → `rails g devise_token_auth:install User auth`
の順でコマンドを実行すれば解決するようです。
ただ今回は学習も兼ねて手動で解決したのであえて、再現する形で解決方法を残します。**


####前提
`devise_token_auth`で認証機能を実装後、後から`devise`のメール認証機能を追加で実装したい。

####詰まったこと
`devise`を用いてメール認証機能を実装しようとしたが、後から`rails g devise:install`が実行できず、
メール認証に必要な設定ファイルが足りていなかった。

`devise`と`devise_token_auth`の両方を用いる場合は
`rails g devise:install` → `rails g devise_token_auth:install User auth`
の順でコマンドを実行する必要があった。
（後から`rails g devise:install`コマンドを実行するとエラーとなる）


####対策
`rails g devise:install`で作成されるファイルを確認し、メール認証に必要なものを手動で作成し編集する。


# 環境

- Rubyバージョン 2.7.1
- Rails 6.1.3

# 実装の流れ
1.Rails(APIモード)でプロジェクトを作成
2.devise_token_authで認証周りの実装
3.confirmableでメール認証機能の追加
4.Action Mailerの設定
5.deviseの設定ファイルを手動で作成  　←今回の詰まったポイント
6.動作確認


## 1.Rails(APIモード)でプロジェクトを作成
APIモードで新規プロジェクトを作成。

```terminal
$ rails new test_api --api
```

### gemのインストール

```ruby:Gemfile
gem 'devise'
gem 'devise_token_auth'
gem 'rack-cors'
```

Gemfileに追記後、`bundle install`する。

```
$ bundle install 
```

### データベースを作成

```
rails db:create
```

## 2.`devise_token_auth`で認証周りの実装
`devise_token_auth`のインストール。
**※正しくは先に`devise`のインストールをすべきですが、今回は後から追加する場合の実装方法を再現するため、あえて`devise_token_auth`のインストールを先に実行しています。**

```
$ rails g devise_token_auth:install User auth
```

###`devise_token_auth_create_users.rb`ファイルの修正
`rails g devise_token_auth:install User auth`コマンドで作成された`devise_token_auth_create_users.rb`ファイルを下記のように修正。
後からメール確認の設定を追加する想定のため、現時点ではConfimableの設定箇所をコメントアウト。

```ruby:db/migrate/20210324032859_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table(:users) do |t|
      ## Required
      t.string :provider, null: false, default: 'email'
      t.string :uid, null: false, default: ''

      ## Database authenticatable
      t.string :encrypted_password, null: false, default: ''

      ## Recoverable
      t.string :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean :allow_password_change, default: false

      ## 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

      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email

      ## Tokens
      t.text :tokens

      t.timestamps
    end

    add_index :users, :email, unique: true
    add_index :users, %i[uid provider], 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
```
###`user.rb`の修正
同じく作成された`user.rb`の内容を修正。
ここでもconfirmableの設定は一旦除外しておきます。

```ruby:app/models/user.rb
# frozen_string_literal: true

class User < ActiveRecord::Base
  extend Devise::Models

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable,
         :registerable,
         :recoverable,
         :rememberable,
         :trackable,
         :validatable
  include DeviseTokenAuth::Concerns::User
end

```
migrateコマンドで、usersテーブルを作成。

```terminal
$ rails db:migrate
```


### `devise_token_auth.rb`の修正
`devise_token_auth.rb`の下記の設定を変更。

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

config.change_headers_on_each_request = false # コメントアウトを外してfalseに変更

config.token_lifespan = 2.weeks # コメントアウトを外す

# コメントアウトを外す
config.headers_names = {:'access-token' => 'access-token',
                        :'client' => 'client',
                        :'expiry' => 'expiry',
                        :'uid' => 'uid',
                        :'token-type' => 'token-type' }
```


### `cors.rb`の設定

`cors.rb`の設定。

```ruby:config/initializers/cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3000' #アクセス元のoriginを設定

    resource '*',
             headers: :any,
             expose: %w[access-token uid client], # トークンの受け渡し用に追加
             methods: %i[get post put patch delete options head]
  end
end
```

### Controllerを作成

```
$ rails g controller auth/registrations
```

####`auth/registrations_controller.rb`の修正


```ruby:auth/registrations_controller.rb
module Auth
  class RegistrationsController < DeviseTokenAuth::RegistrationsController
    private

    def sign_up_params
      params.permit(:name, :email, :password, :password_confirmation)
    end

    def account_update_params
      params.permit(:name, :email)
    end
  end
end

```

### ルートの設定

```ruby:config/routes.rb
Rails.application.routes.draw do
  mount_devise_token_auth_for 'User', at: 'auth', controllers: {
    registrations: 'auth/registrations'
  }
end
```


## 3.confirmableでメール認証機能の追加
ここまでで`devise_token_auth`を用いて認証機能の実装が完了しました。
この状態からメール認証機能の実装をしていきます。


### confirmableの有効化
`user.rb`と`devise_token_auth_create_users.rb`ファイルを修正し、confirmableを有効にする。

```ruby:app/models/user.rb
# frozen_string_literal: true

class User < ActiveRecord::Base
  extend Devise::Models

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable,
         :registerable,
         :recoverable,
         :rememberable,
         :trackable,
         :validatable,
         :confirmable #追記
  include DeviseTokenAuth::Concerns::User
end

```

```ruby:db/migrate/20210324032859_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table(:users) do |t|
      ## Required
      t.string :provider, null: false, default: 'email'
      t.string :uid, null: false, default: ''

      ## Database authenticatable
      t.string :encrypted_password, null: false, default: ''

      ## Recoverable
      t.string :reset_password_token
      t.datetime :reset_password_sent_at
      t.boolean :allow_password_change, default: false

      ## 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

      ## User Info
      t.string :name
      t.string :nickname
      t.string :image
      t.string :email

      ## Tokens
      t.text :tokens

      t.timestamps
    end

    add_index :users, :email, unique: true
    add_index :users, %i[uid provider], 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


```

## 4.Action Mailerの設定
メール送信用のaction mailerの設定（Gmail用の設定）

### `dotenv-rails`のインストール
メールアドレスなど繰り返し使用する値などは外だしした方がいいので`dotenv-rails`Gemをインストール。

```ruby:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'dotenv-rails' # 追加
end
```

```
$ bundle install
```


### メールのアプリパスワードを取得
Action MailerでGmailを利用するにはアプリパスワードが必要なため取得する。
(ご自身のGmailアカウントの設定画面にて２段階認証を行うことで、アプリパスワードが取得できます。)


###`.env`ファイルの設定
`.env`ファイルを作成し、下記内容で保存する。

```terminal:.env
EMAIL_ADDRESS=<ご自身のGmailアドレス>
EMAIL_PASSWORD=<取得したアプリパスワード>
```

## 5.deviseの設定ファイルを手動で作成
ここが今回詰まったポイントです。
この時点で`rails g devise:install`コマンドを実行してもエラーとなります。
そのため`rails g devise:install`コマンドが何をしているか調べて必要なファイルを手動で作成して解決しました。

`rails g devise:install`コマンドでは下記のファイルが作成、追記される。
・config/initializers/devise.rb
・config/locales/devise.en.yml
・config/environments/development.rb 
　config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }が追記

メール認証機能の実装に必要なのは`devise.rb`と`development.rb`の追記内容のため、これらを手動で追加する。

###`devise.rb`の作成
config/initializers配下に`devise.rb`を作成し、下記内容で保存する。

```ruby:config/initializers/devise.rb
Devise.setup do |config|
  config.mailer_sender = ENV['EMAIL_ADDRESS']
  config.mailer = 'Devise::Mailer'
end
```

###`development.rb`への追記
`development.rb`に下記の追加部分の内容を追記。

```ruby:config/environments/development.rb
require 'active_support/core_ext/integer/time'

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

 #〜〜一部省略〜〜

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = true

  config.action_mailer.perform_caching = true
  config.action_mailer.default_url_options = { host: 'localhost:3001' } # 追加
  config.action_mailer.default_options = { from: ENV['EMAIL_ADDRESS'] } # 追加

 #〜〜一部省略〜〜
end

```

### `mail.rb`を作成。
config/initializers配下にメール設定用の`mail.rb`ファイルを作成し、下記内容で保存する。

```ruby:config/initializers/mail.rb
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
  address: 'smtp.gmail.com',
  domain: 'gmail.com',
  port: 587,
  user_name: ENV['EMAIL_ADDRESS'],
  password: ENV['EMAIL_PASSWORD'],
  authentication: 'plain',
  enable_starttls_auto: true,
  }

```


### DB再構築
confirmableのカラムを追加したので、DBを再作成する。

```terminal
$ rake db:migrate:reset
```

## 6.動作確認
これでメール認証機能が実装できたので動作確認をしていきます。

サーバーを起動。
今回ポートは3001で起動します。

```terminal
$ rails s -p 3001
```

Talend API Tester - Free Edition　を使用してAPIの動作確認。
下記リンクからChrome拡張機能に追加できます。
- [Talend API Tester - Free Edition](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm?hl=ja)

### ユーザ新規登録のリクエスト
ユーザ新規登録のURLは`POST`の`auth`なのでTalend API Testerに入力をしてリクエストします。

**リクエスト内容**
・リクエストのMETHODを`POST`に変更する
・URLは`http://localhost:3001/auth`
・`name`、`email`、`password`、`confirm_success_url`それぞれの値を入力(emailはご自身の使用できるメールアドレス)
・ Sendボタンを押下

**確認内容**
・レスポンスが「200 OK」であることを確認
・`email`に指定したメールアドレスにタイトルが「Confirmation instructions」のメールが届く
・メールに記載の「Confirm my account」のリンクをクリックし`confirm_success_url`に指定したURLが表示される

上記が確認できれば動作確認完了です。

↓リクエスト例
![スクリーンショット 2021-05-24 16.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1433539/ea5a966f-b352-f402-bf6c-f745106e6e4f.png)



# 参考文献
- [Rails5 + devise token authで作る 認証API](http://www.webcyou.com/?p=7869)
- [Rails6.0とdevice_token_auth でトークンベースで認証を実装する~confirmable + action mailerの設定まで](https://qiita.com/mtoyopet/items/076b623ac72f4f83c5f6)
- [【Rails備忘録】deviseまとめ] (https://qiita.com/ShinyaKato/items/a098a741a142616a753e)
- [RailsでCORSのAccess-Control-Expose-Headersを設定する](https://qiita.com/cyperaceae/items/a6e5ec7f0cdf89365e8c)
- [rack-corsでCORS設定をする](https://techblog.lclco.com/entry/2018/09/30/200122)
- [Rails の ActionMailer でメール送信処理](https://qiita.com/hirotakasasaki/items/ec2ca5c611ed69b5e85e)