はじめに
この記事はプログラミング初学者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。内容に不備などあればご指摘いただますと幸いです。
今回、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に追加します。
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の初期設定ファイルを作成します。下記のコマンドを実行します。
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を実行して生成されたマイグレーションファイルについて、必要な所があれば修正します。
今回は下記のようにしました。
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
下記のコマンドでテーブルを作成します。
rails db:migrate
docker compose exec api rails db:migrate
APIコントローラーの作成
controllers配下に下記ファイルを作成します。
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を継承しているため、実際の「ユーザーを作成する処理」は裏で親クラスが全部やってくれます。
ルーティングの作成
下記の行を追加します。
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で下記の設定をします。
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を有効にする
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable #追加
include DeviseTokenAuth::Concerns::User
end
letter_opener_webの設定
下記の行を追加します。
group :development do
gem 'letter_opener_web', '~> 3.0'
end
下記のコマンドでインストールします。
bundle install
docker compose exec api bundle install
ブラウザで確認できるようにルーティングを設定
下記の行を追加します。
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
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)側に送信しています。
おわりに
最後まで読んでいただきありがとうございました。
参考にしたサイト