devise_token_authで作る認証機能

## devise_token_authとは？
devise_token_authはRailsでトークンベースの認証機能を作れるgemのこと。
フロントとバックを分けて開発している時なんかに使う。
クライアントは、発行されたトークンを用いてユーザー認証を行う。
メールアドレスを用いた本人確認や、パスワードのリセットといった、webアプリケーションによくある機能もこのgemで作ることができる。

## 実行環境
Ruby 3.3.0
Rails 7.0.8

## 環境設定
まずは必要なgemをインストール。
```
gem 'devise_token_auth'
gem "devise"
gem "rack-cors"
```
```
bundle install
```
:::note info
CORSの制御のため、rack-corsも必要
:::

deviseとdevise_token_authをインストール
```
rails g devise:install
rails g devise_token_auth:install User auth
```
:::note info
末尾にUser authをつけることで、以下の設定になる。
ユーザー認証に使うクラスをUserとする
認証に関連するパスはauth/~となる(https://example.com/auth/sign_in) みたいな感じ
:::

ユーザー認証時のルーティングを設定する。
今回はappディレクトリ配下にapi/v1/authディレクトリを作成し、registrationsコントローラーをオーバーライドする。
```
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      mount_devise_token_auth_for 'User', at: 'auth', controllers: {
        registrations: 'api/v1/auth/registrations'
      }
    end
  end
end
```

ユーザー登録時に認証メールを送信するようにする。
User.rbにconfirmableを追加すればOK
```
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable #　追加
```

あとはmigrateを実行すればOK
```
rails db:migrate
```

## テストメール設定
ユーザー登録の際、メールアドレスを使って本人確認を行う。
ローカル環境で実際にメールを送信するわけにもいかないので、テスト用にletter_opener_webを使用する。
[参考](https://github.com/fgrehm/letter_opener_web)
Gemfileに追加してインストール。
```
group :development do
  gem 'letter_opener_web', '~> 2.0'
end
```
```
bundle install
```
ルーティングの設定
routes.rbに以下を追記。
```
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
```
config/environments/development.rb に以下を追記
- delivery_method
その名の通りメールの配信方法。letter_openerを使用する。
- perform_deliveries
実際にメールを送るかどうか。trueで送る。
```
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
```

これで、http://localhost:3000/letter_opener/ にブラウザからアクセスすればletter_openerのGUIが表示されるはず。
![スクリーンショット 0006-01-28 3.30.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429077/1241a6d8-6e58-d459-4808-3e59a990bd4f.png)

## CORS
APIとは異なるドメインからのリクエストを想定しているので、CORSの設定をする必要がある。
- config/initializers/cors.rb
```
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    # 開発環境の場合は、全てのオリジンからのリクエストを許可する
    if Rails.env.development?
      origins '*'
    else
      origins "example.com"
    end

    resource '*',
      headers: :any,
      expose: ["access-token", "expiry", "token-type", "uid", "client"],
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```

## 実践(ユーザー登録)
テスト用に作ったクソザコフォームでテストしていく。
![スクリーンショット 0006-01-28 5.21.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429077/dadc559c-d2b6-8476-4d3e-26af225ae978.png)

画面ははReactで作成しており、コードは以下のような感じ。
とりあえずこれで実行してみる。
```
import React, { useState } from 'react';
import axios from 'axios';

function UserSignUp() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSignUp = async () => {
    try {
      const response = await axios.post('http://localhost:3000/api/v1/auth', {
        // パラメータとして送っているのはemailとpasswordの二つのみ。
        email: email,
        password: password
      });
      console.log(response.data);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div>
      <input
        type="text"
        placeholder="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button onClick={handleSignUp}>Sign Up</button>
    </div>
  );
}

export default UserSignUp;

```

実行すると、API側で422エラーが起きて「Unpermitted parameter: :registration.」というメッセージが出ているはず。
rails側でparamsをwrapしてしまい、以下のようなパラメーターが送られていたため、このような挙動になるらしい。
```
Parameters: {"email"=>"test@example.com", "password"=>"[FILTERED]", "registration"=>{"email"=>"test@example.com", "password"=>"[FILTERED]"}}
```

registrationsコントローラーのsign_up_paramsメソッドをオーバーライドし、registrationからパラメータを取得するようにします。
- app/controllers/api/v1/auth/registrations_controller.rb
```
class Api::V1::Auth::RegistrationsController < DeviseTokenAuth::RegistrationsController
  private
  def sign_up_params
    params.require(:registration).permit(:email, :password)
  end
end
```

もう一度実行すると、usersテーブルにレコードが作成されました！
![スクリーンショット 0006-01-28 6.00.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429077/fa4f2078-4947-02db-d5cd-d88d62a3fc39.png)

letter_openerを開くと、アカウント認証メールが届いているはずです。
![スクリーンショット 0006-02-05 4.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429077/860dcd9c-8a13-118d-c11a-b5e02803bd4b.png)

Confirm my accountをクリックすると、対象ユーザーのconfirmed_atが更新される。これでユーザー登録完了！
![スクリーンショット 0006-02-05 4.10.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429077/e76c6ecc-ff43-5b59-efd5-1e123dfd9dae.png)