Rails6.0とdevice_token_auth でトークンベースで認証を実装する~confirmable + action mailerの設定まで


## はじめに
この記事では、下記を解説していきます.


- RailsのAPIモードと`devise_token_auth` gemを使って、トークンベースで認証を実装する
- confirmableを有効化
- Gメールアカウント ＋ Action mailerでメール送信

## 前提条件

- Rubyバージョン 2.5.1
- Rails 6.0
- PostgreSQL

## APIモードでRailsプロジェクトを作成
まずはAPIモードで新規プロジェクトを作成します。コマンドは、`rails new <アプリ名> -T -d postgresql --api` です。

```terminal
$ rails new rails_app -T -d postgresql --api
```

**オプションコマンドの説明**

- `-T` → デフォルトのテスト用ライブラリのインストールをスキップする為のオプションコマンド (Rspecなどを使う際はこのオプションコマンドを使ってください）
- `-d postgresql` → デフォルトのデータベースをpostgresqlに指定するためのオプションコマンド（指定しない場合は、sqliteになる)
- `--api` → APIモードでRailsアプリを作成するためのオプションコマンド

## Railsのバージョンを6に変更
Rails newした時点では、バージョン5でRuby on Railsアプリが作られています。今回はRails6に手動でバージョン変更します。

```ruby:Gemfile
gem 'rails', '~> 6.0.0'
```

## 必要なgemを追加
今回は`devise`、`devise_token_auth`、`rack-cors`の３つを使っていきます。下記3つをGemfileに追加しましょう。

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

### インストール
`bundle install`しましょう。 インストールが完了した後に、`Gemfile.lock`をみてRails6になっているか確認すると安心です。

```
$ bundle install --path vendor/bundle
```


## データベースを作成
gemのインストールが完了したら、データベースを作成しておきましょう。

```
rails db:create
```

## deviseをインストール
`devise_token_auth`を設定していく前に、deviseをインストールしておきます。手順が逆になるとエラーが出る可能性があるので気をつけてください。

```
$ rails g devise:install
```

上のコマンドを実行すると下記のような、表示が返ってきます。

```
      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_token_auth`をインストール＆Useモデルの作成
今度は`devise_token_auth`のインストールを実施します。繰り返しますが、deviseのインストールと順番を間違えるとエラーが出る可能性があるため、必ずdeviseインストール→`devise_token_auth`のインストールの順で実施して下さい。

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

実行が成功すると、下記のような表示が返ってきます。

```
      create  config/initializers/devise_token_auth.rb
      insert  app/controllers/application_controller.rb
        gsub  config/routes.rb
      create  db/migrate/20200610084840_devise_token_auth_create_users.rb
      create  app/models/user.rb
```

`rails g devise_token_auth:install User auth`コマンドで作成された`devise_token_auth_create_users.rb`ファイルを開きます。これからusersテーブルを作成します。

```ruby:db/migrate/20200610084840_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.0]
  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

      ## 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.json :tokens

      t.timestamps
    end

    add_index :users, :email,                unique: true
    add_index :users, [: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
```

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

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

下記のような表示がされたら成功です。

```
== 20200509111809 DeviseTokenAuthCreateUsers: migrating =======================
-- create_table(:users)
   -> 0.0229s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0156s
-- add_index(:users, [:uid, :provider], {:unique=>true})
   -> 0.0051s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0070s
== 20200509111809 DeviseTokenAuthCreateUsers: migrated (0.0509s) ==============

```

## 設定を変更
これから、`devise_token_auth`が使える様に設定を変更していきます。

### 1.`devise_token_auth.rb`の設定
まずは`devise_token_auth.rb`を開き、下記の設定を変更します。

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

config.change_headers_on_each_request = false # コメントを外してtrueからfalseに変更する

config.token_lifespan = 2.weeks # コメントをはずす

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

- `config.change_headers_on_each_request` → `true`の場合、**リクエストごとにtokenを新しくする必要がある**、という設定になります。毎回、毎回、トークンが変更されるという動きは今回は期待していないので、`false`に変更します。

- `token_lifespan` → 名前の通り、tokenの有効期間ですね。今回は、２週間に設定しておきます。

- `headers_names`  → 認証用ヘッダーの名前の定義です。必要でない限り、変更する必要はないでしょう。

### 2.`application_controller.rb`の設定
次に、`application_controller.rb`に設定を追加していきます。この一文をすることで`SetUserByToken`という拡張機能を使える様になります。CookieやCORSの設定ですね。

```ruby:application_controller.rb
class ApplicationController < ActionController::API
  # 下記一行を追加
  include DeviseTokenAuth::Concerns::SetUserByToken
end
```

### 3.`application.rb`の設定

最後に`application.rb`の設定です。`# ここからコピペする`から`# ここまで`までコピーペーストしてください。

```ruby:config/application.rb
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module DeviseTokenAuthTwitter
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
    
    # ここからコピペする
    config.session_store :cookie_store, key: '_interslice_session'
    config.middleware.use ActionDispatch::Cookies # Required for all session management
    config.middleware.use ActionDispatch::Session::CookieStore, config.session_options
    config.middleware.use ActionDispatch::Flash
    config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins '*'
        resource '*',
                 :headers => :any,
                 :expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
                 :methods => [:get, :post, :options, :delete, :put]
      end
    end
    # ここまで
  end
end

```

## ルートの設定
今度はルートを変更していきます。`mount_devise_token_auth_for`以降を`routes.rb`にコピーペーストしてください。

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

## Controllerを作成

`registrations`コントローラーを上書きする必要があるため、railsコマンドを実行してコントローラーを作成します。

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

`auth/registrations_controller.rb`に下記をコピーペーストしてください。。

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

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

```
今回は、Userを登録する時に`name`も登録させたいため、`strong_parameter`に`name`も追加しています。

## 動くかテスト！

動くかテストしていきます。サーバーを起動します。

```terminal
$ rails s
```

今回は[postman](https://www.postman.com/)を使ってHTTPリクエストを送信します。


### 1.ユーザ新規登録
ユーザ新規登録のURLは`POST`の`auth`です。

1. postmanのメソッドを`POST`に変更する
2. URLに`http://localhost:3000/auth`を入力する
3. `Body`タブでform-dataを選択する
4. `email`、`password`、`password_confirmation`とそれぞれの値を入力する
5. Sendボタンを押下する
6. レスポンスが返ってくる

すると、レスポンスのBodyに`success`と表示されます。これが表示されたら、アカウント登録は成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/84f6e73c-70df-67cd-0228-e6326dad2a53.png)



##2. ログインと認証用データの取得
今度は先ほど作成したアカウントで、ログインを試してみます。ログインは`POST`の`auth/sign_in`です。先ほど登録した`email`と`password`をBodyに付与して送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/ce124a3b-e0e1-d5dd-d2e0-f58a6b69d235.png)


ログインが成功したらレスポンスのBodyは⬆️のように返ってきます。ここで注目してもらいたいのが、レスポンスのHeadersです。ログインが成功すると、レスポンスのHeadersに`access-token`、`client`、`uid`の3つの情報が入って返ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/0bf4735e-b702-000d-6c45-118be6d1b57d.png)


この3つの情報をリクエストの度にリクエストヘッダーに付与することで認証を行います。言い換えると、リクエストを送信する度にリクエストヘッダーにこの3つを付与することで、「これがログイン情報だよ、すでに私はログイン済みだよ」とRailsアプリに教えてあげます。



###3. パスワード変更
さてここで、認証されていない（ログインしていない）とできないものを試してみましょう。パスワード変更です。パスワード変更はログインしている必要があります。言い換えると、リクエストヘッダーに`access-token`、`client`、`uid`を付与する必要があります。

1. メソッドを`PUT`に変更する
2. URL欄に`http://localhost:3000/auth`を入力する
3. Headersタブを選択する
4. ログイン時のレスポンスで返ってきた`access-token`、`client`、`uid`と値を入力する
5. Bodyタブを選択する
6. 新しいパスワードを入力する
7. Sendボタンを押下する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/2d306abf-7656-edae-1414-8e72e552e03e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/5b893967-bc33-7a80-21a7-2376036709de.png)


すると、successのレスポンスが返ってきます。`devise_token_auth`の基本設定はここまでになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/0e845fc6-9c74-5a17-3c13-296a5d49460a.png)


## Confirmableの設定
ここからはConfirmableの設定と、action mailerを使ってアカウント確認用メールを送信する方法になります。

confirmableを設定すると、ユーザが新たにアカウント登録すると、登録したメールアドレスにメールが送信され、そのメール内の「アカウント確認」を押すまで本登録が完了しない様にできます。

### confirmableを有効にする
Userテーブルでconfirmableを有効にします。

```ruby:app/models/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable # ← confirmableを追加する
  include DeviseTokenAuth::Concerns::User

end
```

## Action Mailerの実装
ここからは、メール送信用のaction mailerを設定していきます。今回はGメールを使うので、Gメール使用での設定になります。

### 1.`env`の準備
`.env`を使うので`dotenv-rails`Gemを導入し、`bundle install`ます。

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

.envファイルを作成しましょう。

```
$ touch .env
```

### 2.メールのアプリパスワードを取得
Action MailerでGメールを利用するには、アプリパスワードを取得する必要があります。
Gmailで２段階認証をオンにし、アプリパスワードを取得してください。

- [app passwords](https://myaccount.google.com/apppasswords)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/f0e45515-dd9c-64b8-e8eb-532898256e74.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/67cde7da-71aa-641e-0fde-ac5463a71f0b.png)

`.env`に上で取得したアプリパスワードとGメールアドレスを入れます。

```terminal:.env
EMAIL_ADDRESS=test@gmail.com
EMAIL_PASSWORD=<生成されたアプリのパスワード>
```

### 3.メールの設定を追加
`devise.rb`にmailerの設定を追加します。

```ruby:config/initializers/devise.rb
Devise.setup do |config|
  config.mailer_sender = ENV['EMAIL_ADDRESS'] # コメントアウトをはずして変更
  config.mailer = 'Devise::Mailer' # コメントアウトを外す
end
```

次に、`development.rb`に下記の設定を追加します。

```ruby:config/environments/development.rb
Rails.application.configure do
  # 下記を追加
  config.action_mailer.default_options = { from: ENV['EMAIL_ADDRESS'] }
  config.action_mailer.default_url_options = { host: 'localhost:3000' }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address: 'smtp.gmail.com',
    port: 587,
    domain: 'gmail.com',
    user_name: ENV['EMAIL_ADDRESS'],
    password: ENV['EMAIL_PASSWORD'],
    authentication: 'plain',
    enable_starttls_auto: true
  }
end
```

### 4.動くかチェック！
さてここで、confirmableが動くかチェックします。

`POST`の`auth`にリクエストを送信しますが、confirmableを設定すると、リクエストパラメターに`confirm_success_url`を追加する必要があります。

登録のメールアドレス宛にメールがきて「アカウント確認」ボタンを押した後、どこにユーザをリダイレクトさせるかを定義するためのパラメターです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/3fc083d4-722e-e45d-57e7-97ce6e12884d.png)

メールがきました！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/8ee63038-ff0f-a8b4-0163-e9594440809b.png)

流れは下記です。

1. 「Confirm my account」を押す
2. urlのパラメターに入っているconfirmation_tokenとDBのconfirmation_tokenが一致しているかチェック
3. 一致していればそのユーザーのconfirmed_atカラムにその時の日付が入る＆urlのパラメターのredirect_urlにリダイレクトする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/aa0f3a83-88bf-a4a0-cc9f-10fd40bd0a6e.png)

### 日本語化
さて、メールが英語だといろいろと不便なので、i18nを追加して日本語対応させましょう。

### 1.Gemファイルに追加
Gemファイルに下記を追加し、`bundle install`します。

```ruby:Gemfile
gem 'devise-i18n'
gem 'devise-i18n-views'
```

```
$ bundle install
```

### 2.日本語訳用ファイルの作成
日本語訳用ファイルを作成します。

```
rails g devise:views:locale ja

      create  config/locales/devise.views.ja.yml
```

```yml:config/locales/devise.views.ja.yml
ja:
  activerecord:
    attributes:
      user:
        current_password: "現在のパスワード"
        email: "メールアドレス"
        password: "パスワード"
        password_confirmation: "確認用パスワード"
        remember_me: "ログインを記憶"
    models:
      user: "ユーザ"
  devise:
    confirmations:
      new:
        resend_confirmation_instructions: "アカウント確認メール再送"
    mailer:
      confirmation_instructions:
        action: "アカウント確認"
        greeting: "ようこそ、%{recipient}さん!"
        instruction: "次のリンクでメールアドレスの確認が完了します:"
      reset_password_instructions:
        action: "パスワード変更"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "誰かがパスワードの再設定を希望しました。次のリンクでパスワードの再設定が出来ます。"
        instruction_2: "あなたが希望したのではないのなら、このメールは無視してください。"
        instruction_3: "上のリンクにアクセスして新しいパスワードを設定するまで、パスワードは変更されません。"
      unlock_instructions:
        action: "アカウントのロック解除"
        greeting: "こんにちは、%{recipient}さん!"
        instruction: "アカウントのロックを解除するには下のリンクをクリックしてください。"
        message: "ログイン失敗が繰り返されたため、アカウントはロックされています。"
    passwords:
      edit:
        change_my_password: "パスワードを変更する"
        change_your_password: "パスワードを変更"
        confirm_new_password: "確認用新しいパスワード"
        new_password: "新しいパスワード"
      new:
        forgot_your_password: "パスワードを忘れましたか?"
        send_me_reset_password_instructions: "パスワードの再設定方法を送信する"
    registrations:
      edit:
        are_you_sure: "本当に良いですか?"
        cancel_my_account: "アカウント削除"
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: "空欄のままなら変更しません"
        title: "%{resource}編集"
        unhappy: "気に入りません"
        update: "更新"
        we_need_your_current_password_to_confirm_your_changes: "変更を反映するには現在のパスワードを入力してください"
      new:
        sign_up: "アカウント登録"
    sessions:
      new:
        sign_in: "ログイン"
    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: "アカウント登録"
    unlocks:
      new:
        resend_unlock_instructions: "アカウントの凍結解除方法を再送する"
```

### 3.動くかチェック
さてここで、メールが日本語化されるかチェックしましょう。・・・・日本語化されてる！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/ec63ba3a-c131-9a38-f555-ba4c2962e79d.png)

## 4.だがしかしまだだ

メール本文の「アカウント登録」を押すと、リダイレクトされず、下記画像の様な「**ArgumentError in DeviseTokenAuth::ConfirmationsController#show**」「**bad argument (expected URI object or URI string)**」というエラーが表示される様になってしまいました。

![スクリーンショット 2020-06-15 13.54.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352778/1e6bb893-0d00-4edb-18aa-0f425a8b5e7a.png)

これは、parameterで送信した`confirm_success_url`が「アカウント確認」のURLに設定されていないためです。

1. アカウント確認を押す
2. URLのparamterについているconfirmation_tokenとデータベース上に保存されているconfirmatioin_tokenが一致する確認
3. 一致すればparameterについているconfirm_success_urlにリダイレクトさせる
4. アカウント登録完了

という流れになりますが、i18nを設定した際に、ここの設定がおかしくなる様です・・・、confirmation時のメールを上書きする必要があります。

証拠に、「アカウント確認」のURLをみてみると、`http://localhost:3000/auth/confirmation?config=default&confirmation_token=yVjqfL7Rrr57CqDqU62v`といった感じになっているはず。

本来であれば、`http://localhost:3000/auth/confirmation?config=default&confirmation_token=yVjqfL7Rrr57CqDqU62v&redirect_url=https%3A%2F%2Ftestapp.com`の様に、`redirect_url`パラメターが付与されている必要があります。

**「`i18n`をインストールする時点でメールも上書きしてくれよ・・・」**という不満が声に出そうになるのをぐっと抑えて設定していきます。

### 5.viewsファイルを作成

下記コマンドを実行して、メール送信用のviewファイルを作成します。

```terminal
$ rails generate devise:views
```

### 6.`confirmation_instructinos`を修正する

`/app/views/devise`ディレクトリが作成されています。アカウント確認時に使用される`confirmation_instructinos.html.erb`を修正していきます。

```erb:/view/devise/mailer/confirmation_instructinos.html.erb
<p><%= t(:welcome).capitalize + ' ' + @email %>!</p>

<p><%= t '.instruction' %> </p>

<%=# 下記の様に書かれているはずなので %>
<%# <p><%= link_to t('.action'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s}).html_safe</p> %> %>

<%=# こちらにいれかえる %>
<%=# redirect_url: message['redirect-url'] が最後にたされている %>
<p><%= link_to t('.action'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %></p>
```

### 7.今度こそ！

改めて試すと、うまくいくはずです！！！

## 宣伝： Railsアプリの作り方を学べる教材を公開しています!!

Techpit様にて、Ruby on RailsとVue.jsでリアルタイムチャットアプリを作る教材を公開しています。
本記事で紹介した認証はもちろんのこと、HTTP通信やAction Cableを使用したWebsocket通信についても詳しく説明しています。Rails APIモードでアプリケーションを作って見たい方はぜひ購入してください！

https://www.techpit.jp/courses/195


## 参考
- [Rails5 + devise token authで作る 認証API](http://www.webcyou.com/?p=7869)
- [【Rails5】Devise-i18nで日本語化する] (https://remonote.jp/rails-devise-i18n-locale-ja)