[Rails] devise token auth を使う

devise token authのチュートリアルのような記事です。
大部分は[この](http://clc.gonna.jp/2017/01/post-1306/#i-3)記事を参考にしています。一部、バージョンの違い（？）によるのか、エラーが出る箇所があったため、この記事を作りました。

# セットアップ
はじめにRailsアプリを作ります。

```bash:terminal
rails new devise_token_auth_test
```

Gemfileにdevise token auth用のgemを追記します。

```ruby:Gemfile
# devise関連
gem 'devise'
gem 'devise_token_auth'

# CORS設定
gem 'rack-cors'
```

``gem 'rack-cors'``を入れる理由については[公式Docs](https://devise-token-auth.gitbook.io/devise-token-auth/)の[Cross Origin Requests (CORS)](https://devise-token-auth.gitbook.io/devise-token-auth/config/cors)に載っています。

bundle install した後データベースを作成します。

```bash:terminal
bundle install
rake db:create
```

# devise token auth の導入

installします。
通常のdeviseについては、[公式Docs](https://devise-token-auth.gitbook.io/devise-token-auth/)にインストールしてくださいとは載っていませんが、``rails s ``時にエラーが出るのでインストールします。

```bash:terminal
rails g devise:install
rails g devise_token_auth:install User auth
```

次に、インストール時に作成された``db/migrate/~_devise_token_auth_create_users.rb``に``Trackable``に関するカラムを追記します。
これがないと、サインイン時に``Undefined method current_sign_in_at``（``current_sign_in_at``はTrackableに関するカラム）とエラーが出ます。

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

```


では、devise token auth の設定を追加します。

```ruby:config/initializers/devise_token_auth.rb
DeviseTokenAuth.setup do |config|
  # リクエストごとにトークンを更新するか
  # 扱いやすいようにFalseにします
  config.change_headers_on_each_request = false
  
  # トークンの有効期間
  # デフォルトでは2週間です
  config.token_lifespan = 2.weeks

  # ヘッダーの名前対応
  config.headers_names = {:'access-token' => 'access-token',
                          :'client' => 'client',
                          :'expiry' => 'expiry',
                          :'uid' => 'uid',
                          :'token-type' => 'token-type' }
end
```

また、``application_controller``に以下を追記してください。

```ruby:controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken
  skip_before_action :verify_authenticity_token, if: :devise_controller? # APIではCSRFチェックをしない
end

```

これで準備が出来ました。

# 試す

サーバーを起動して動かしてみましょう。

```bash:terminal
rails s
```

## サインアップ

```bash:terminal
curl localhost:3000/api/auth -X POST -d '{"email":"example@example.com", "password":"password", "password_confirmation": "password"}' -H "content-type:application/json"
```

curlを使ってUserを作成するAPI(``localhost:3000/api/auth``)を叩いています。
引数の説明は以下の通りです。

``-X POST``　POSTでリクエスト
``-d 'foo:bar'``　Bodyに入れるデータ
``-H 'foo:bar'``　ヘッダーに入れるデータ

今回は、Bodyに``email``・``password``を 、ヘッダーに、Bodyの中身がjson形式であることを示すための設定``content-type:application/json``を追加しています。

サインアップに成功すると以下のようなレスポンスが返ってくるはずです。

```bash:terminal
{"status":"success","data":{"id":1,"provider":"email","uid":"example@example.com","allow_password_change":false,"name":null,"image":null,"email":"example2@example.com","created_at":"2019-05-12T20:48:24.000+09:00","updated_at":"2019-05-12T20:48:24.000+09:00"}}
```

## サインイン

```bash:terminal
curl localhost:3000/api/auth/sign_in -X POST -d '{"email":"example@example.com", "password":"password"}' -H "content-type:application/json" -i
```

``-i``オプションを付けてレスポンスのヘッダーも表示されるようにしています。
（ここの情報をこの後使います。）

成功すると以下のようなレスポンスが返ってくるはずです。

```bash:terminal
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: xzTWhTURMmPdDjaKB2R9gw
token-type: Bearer
client: S2T6zY0aDB6VBXYVMdpjbg
expiry: 1558871460
uid: example@example.com
Cache-Control: no-store, must-revalidate, private, max-age=0

{"data":{"id":1,"email":"example@example.com","provider":"email","uid":"example@example.com","allow_password_change":false,"name":null,"image":null}}
```

## パスワードの変更

```bash:terminal
curl localhost:3000/api/auth/password -X PUT -d '{"password":"password_new", "password_confirmation": "password_new"}' -H "content-type:application/json" -H "access-token: xzTWhTURMmPdDjaKB2R9gw" -H "client: S2T6zY0aDB6VBXYVMdpjbg" -H "uid: example@example.com"
```

パスワードの変更は``POST``ではなく``PUT``を指定します。
また、ログイン時のレスポンスのヘッダーにあった``access-token`` ``client`` ``uid``をヘッダーに追加しています。

成功すると以下のようなレスポンスが返ってくるはずです。

```bash:terminal
{"success":true,"data":{"id":1,"provider":"email","allow_password_change":false,"email":"example@example.com","uid":"example@example.com","name":null,"image":null,"created_at":"2019-05-12T20:30:50.000+09:00","updated_at":"2019-05-12T21:31:33.000+09:00"},"message":"パスワードの更新に成功しました。"}
```

# deviseのコントローラーを編集したい時

```bash:terminal
rails g controller api/auth/registrations
```

```ruby:controllers/api/auth/registrations_controller.rb
class Api::Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
  # hogehoge
end
```

```ruby:config/routes.rb
Rails.application.routes.draw do

  namespace :api do
    mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/auth/registrations'
    }
  end

end
```