はじめに
この記事はプログラミング初学者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。内容に不備などあればご指摘いただますと幸いです。
今回、Rails(API) x ReactでX(旧Twitter)のクローンサイトを作る際にログイン機能を実装しました。
そのログイン機能の実装方法について備忘録も兼ねて記事を作成していきます。
この記事では、devise_token_authを使って新規登録機能を実装した後、ログイン機能を追加します。必要なgemや設定がまだ済んでいない方は、先に下記の記事を参考に設定をお願いします。
devise_token_authで新規登録機能を実装する
ルーティングの設定
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
これで api/v1/users/sign_in や /api/v1/users/sign_out が生成されます。
CORSの設定
config/initializers/cors.rbで下記の設定をします。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:5173'
resource '*',
headers: :any,
# exposeの行を追記
expose: %w[access-token expiry token-type uid client],
methods: %i[get post put patch delete options head]
end
end
expose にトークン関連ヘッダーを含めるのがポイントです。
これをしないと、フロント側からヘッダーの値が取得できません。
devise_token_auth.rbの設定
config/initializers/devise_token_auth.rbで下記のコードをコメントアウトします。
DeviseTokenAuth.setup do |config|
# 認証トークンをリクエストごとに更新するかどうかを指定する設定
config.change_headers_on_each_request = false # trueからfalseに変更
# トークンの有効期間を決める設定
config.token_lifespan = 2.weeks
# トークン認証に使うHTTPヘッダーのキー名を定義する設定
config.headers_names = {
:'authorization' => 'Authorization',
:'access-token' => 'access-token',
:'client' => 'client',
:'expiry' => 'expiry',
:'uid' => 'uid',
:'token-type' => 'token-type'
}
end
フロントエンド側の実装
import axios from "axios";
const apiClient = axios.create({
baseURL: "http://localhost:3000/api/v1",
});
export default apiClient;
上記では、baseURLをセットしたaxiosインスタンスを作成して、以下のコンポーネント内で axios を使用する際のURLを簡略化しています。
Axios インスタンス
import Modal from "react-modal";
import XLogo from "./XLogo";
import ModalCloseButton from "./ModalCloseButton";
import TextInput from "./TextInput";
import { useState } from "react";
import apiClient from "../../apis/apiClient";
import { useNavigate } from "react-router-dom";
const LogInModal = ({ isOpen, onClose }) => {
const initialLogInData = {
email: "",
password: "",
};
const [logInData, setLogInData] = useState(initialLogInData);
const navigate = useNavigate();
const handleChange = (e) => {
setLogInData({ ...logInData, [e.target.name]: e.target.value });
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const res = await apiClient.post("/users/sign_in", logInData);
localStorage.setItem("access-token", res.headers["access-token"]);
localStorage.setItem("client", res.headers["client"]);
localStorage.setItem("uid", res.headers["uid"]);
navigate("/home");
} 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">Xにログイン</h1>
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
<div className="flex flex-col gap-7">
<div>
<TextInput
type="email"
name="email"
placeholder="メールアドレス"
value={logInData.email}
onChange={handleChange}
/>
</div>
<div>
<TextInput
type="password"
name="password"
placeholder="パスワード"
value={logInData.password}
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>
<div className="mb-6 text-gray-500">
アカウントをお持ちでない場合は
<span className="text-blue-400">登録</span>
</div>
</form>
</div>
</Modal>
);
};
export default LogInModal;
上記の処理では、フォームから入力された値を state に保持し、axios を使用して POST メソッドを実行することで、email などのパラメーターをバックエンド(Rails)側に送信しています。サーバー(Rails)がログインに成功すると、レスポンスヘッダーに認証トークン情報を含めて返します。そのトークン類をログアウト機能などの今後の API 通信に利用するため、ローカルストレージに保存しています。
おわりに
最後まで読んでいただきありがとうございました。
参考にしたサイト