0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

devise_token_authで新規登録機能を実装する

0
Posted at

はじめに

この記事はプログラミング初学者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。内容に不備などあればご指摘いただますと幸いです。

今回、Rails(API) x ReactでX(旧Twitter)のクローンサイトを作る際に新規登録機能を実装しました。
その新規登録機能の実装方法について備忘録も兼ねて記事を作成していきます。

この記事では下記を解説しています。

  • devise_token_auth を用いてトークンベース認証を実装。
  • letter_opener_web を使って開発環境でメール内容をブラウザ確認できるように設定。

devise_token_auth とは?

  • devise_token_auth は、通常の Devise(セッションベース認証)を
    API向けにトークンベース認証に拡張した gem です。
  • これにより、SPA(React/Vue/Next.js など)やモバイルアプリ と連携できるようになります。

必要なgemのインストール

下記の行をGemfileに追加します。

Gemfile
gem 'devise'
gem 'devise_token_auth'
gem 'rack-cors'

下記のコマンドでインストールします。

ターミナル
bundle install

私の場合はDockerで実装していたので下記のコマンドでサーバーを立てて

ターミナル
docker compose up

下記のコマンドでインストールしました。

ターミナル
docker compose exec api(サービス名) bundle install

初期設定ファイルを作成

deviseとdevise_token_authの初期設定ファイルを作成します。下記のコマンドを実行します。

ターミナル(通常 or Docker版)
rails g devise:install
docker compose exec api(サービス名) rails g devise:install

rails g devise_token_auth:install User(モデル名) users(マウント先パス)
docker compose exec api(サービス名) rails g devise_token_auth:install User(モデル名) users(マウント先パス)

rails g devise_token_auth:install User usersのusersはエンドポイントパスを指定しています。
これは、以下のようなエンドポイントになります。
/users
/users/sign_in
/users/sign_out

Usersテーブルを作成

rails g devise_token_auth:install User usersを実行して生成されたマイグレーションファイルについて、必要な所があれば修正します。
今回は下記のようにしました。

db/migrate/○○○_devise_token_auth_create_users.rb
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[7.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, null: false #追加
      t.string :email, null: false
      t.date :birth_date, null: false #追加
      t.text :self_introduction #追加
      t.string :place #追加
      t.text :website #追加

      ## 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
    add_index :users, :name,                 unique: true #追加
  end
end

下記のコマンドでテーブルを作成します。

ターミナル(通常 or Docker版)
rails db:migrate
docker compose exec api rails db:migrate 

APIコントローラーの作成

controllers配下に下記ファイルを作成します。
controllers/api/v1/registrations_controller.rb

下記の行を追加します。

app/controllers/api/v1/registrations_controller.rb
module Api
  module V1
    class RegistrationsController < DeviseTokenAuth::RegistrationsController
      private

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

ここではストロングパラメータしか記述していませんが、DeviseTokenAuth::RegistrationsControllerを継承しているため、実際の「ユーザーを作成する処理」は裏で親クラスが全部やってくれます。

ルーティングの作成

下記の行を追加します。

config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      mount_devise_token_auth_for 'User', at: 'users', controllers: {
        registrations: 'api/v1/registrations'
      }
    end
  end
end

namespaceはルーティングを「名前空間(namespace)」でグループ化する構文です。
ここでは、URLパスが /api/v1/... の形になります。

mount_devise_token_auth_for 'User', at: 'users'のat: 'users'では、エンドポイントを指定しています。上記により、/api/v1/users が自動生成します。

controllers: { registrations: 'api/v1/registrations' }では、上記で作成したコントローラを指定しています。/api/v1/users でアクセスしたときにapp/controllers/api/v1/registrations_controller.rbを使うように設定しています。

CORSの設定

CORS(Cross-Origin Resource Sharing:クロスオリジンリソースシェアリング)は、異なるオリジン間での通信を制御し、安全に行うための仕組みです。
React(5173)とRails(3000)のようにオリジンが異なる場合、ブラウザが通信を制限するため、CORS設定によって許可を与える必要があります。

config/initializers/cors.rbで下記の設定をします。

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "http://localhost:5173" # ReactサーバーのURL

    resource "*", # すべてのエンドポイントに適用
      headers: :any, # すべてのヘッダーを許可
      expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'], #フロント側(React)に特定のレスポンスヘッダーを見えるようにする設定
      methods: [:get, :post, :put, :patch, :delete, :options, :head] # 許可するHTTPメソッドを定義
  end
end

メール認証の導入

confirmableを有効にする

app/models/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable #追加
  include DeviseTokenAuth::Concerns::User
end

letter_opener_webの設定

下記の行を追加します。

Gemfile
group :development do
  gem 'letter_opener_web', '~> 3.0'
end

下記のコマンドでインストールします。

ターミナル(通常 or Docker版)
bundle install
docker compose exec api bundle install

ブラウザで確認できるようにルーティングを設定

下記の行を追加します。

config/routes.rb
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?

development.rbの設定

下記の行を追加します。

config/environments/development.rb
  # メール送信失敗時にエラーを出すかの設定
  config.action_mailer.raise_delivery_errors = true

  # メール内容をキャッシュするかの設定
  config.action_mailer.perform_caching = false
  
  # メール内のリンクで使うURLを指定する設定
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

  # メールの送信方法を設定
  config.action_mailer.delivery_method = :letter_opener_web

  # 実際にメール送信処理を行うかの設定
  config.action_mailer.perform_deliveries = true

フロントエンド側の実装

import Modal from "react-modal";
import XLogo from "./XLogo";
import ModalCloseButton from "./ModalCloseButton";
import TextInput from "./TextInput";
import { useState } from "react";
import axios from "axios";

Modal.setAppElement("#root");

const SignUpModal = ({ isOpen, onClose }) => {
  const initialSignUpData = {
    name: "",
    email: "",
    birth_date: "",
    password: "",
    password_confirmation: "",
    confirm_success_url: "http://localhost:5173",
  };

  const [signUpData, setSignUpData] = useState(initialSignUpData);

  const handleChange = (e) => {
    setSignUpData({ ...signUpData, [e.target.name]: e.target.value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      const res = await axios.post(
        "http://localhost:3000/api/v1/users",
        signUpData,
      );
      setSignUpData(initialSignUpData);
      onClose();
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <Modal
      isOpen={isOpen}
      className="relative mx-auto mt-16 flex h-163 w-150 flex-col items-center rounded-2xl bg-black px-20 pt-2"
      overlayClassName="fixed inset-0 bg-sky-200/20"
    >
      <ModalCloseButton onClick={onClose} />
      <XLogo size={31} />
      <div className="flex h-full w-full flex-col">
        <h1 className="py-7 text-3xl font-semibold">アカウントを作成</h1>
        <form onSubmit={handleSubmit} className="flex flex-1 flex-col">
          <div className="flex flex-col gap-7">
            <div>
              <TextInput
                name="name"
                placeholder="名前"
                value={signUpData.name}
                onChange={handleChange}
              />
            </div>
            <div>
              <TextInput
                type="email"
                name="email"
                placeholder="メールアドレス"
                value={signUpData.email}
                onChange={handleChange}
              />
            </div>
            <div>
              <TextInput
                name="birth_date"
                placeholder="生年月日"
                value={signUpData.birth_date}
                onChange={handleChange}
              />
            </div>
            <div>
              <TextInput
                type="password"
                name="password"
                placeholder="パスワード"
                value={signUpData.password}
                onChange={handleChange}
              />
            </div>
            <div>
              <TextInput
                type="password"
                name="password_confirmation"
                placeholder="パスワード確認用"
                value={signUpData.password_confirmation}
                onChange={handleChange}
              />
            </div>
          </div>
          <button
            type="submit"
            className="my-6 mt-auto w-full cursor-pointer rounded-full border bg-white py-3.5 font-bold text-black transition-colors duration-200 hover:bg-gray-200"
          >
            登録する
          </button>
        </form>
      </div>
    </Modal>
  );
};

export default SignUpModal;

axiosは、フロントエンドでHTTP通信を行う際に必要な処理をまとめて行ってくれるライブラリです。Promiseベースで動作しています。(Promiseを返すということ)

上記の処理では、フォームから入力された値を state に保持し、axios を使用して POST メソッドを実行することで、email などのパラメーターをバックエンド(Rails)側に送信しています。

おわりに

最後まで読んでいただきありがとうございました。

参考にしたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?