Closed8
remix-auth-oauth2でOAuth2認証を楽にしたい

reactrouterでOAuth認証をスクラッチ実装してたけど、remix-auth使ったら次から実装楽になるんじゃないのかって思ったから使ってみる

-
remix-auth
sergiodxa/remix-auth: Simple Authentication for Remix -
remix-auth-oauth2: OAuth2用のremix-authストラテジー
sergiodxa/remix-auth-oauth2: A OAuth2Strategy for Remix Auth

Claudeに聞いたり、ネットの記事を参考にして実装しようとしてたけどなんか噛み合わないなあと思ったら、v4でだいぶ変わったらしい
remix-authをv4にアップデートする

こんな感じでauthenticator
を用意する。
import { Authenticator } from "remix-auth";
import { OAuth2Strategy } from "remix-auth-oauth2";
import { commitSession, getSession } from "./session.ts";
// 取得するユーザー情報の型
type User = {
id: number;
name: string;
roleType: number;
mailAddress: string;
userId?: string;
};
// セッションに保存するユーザー情報の型
export type SessionUser = User & {
accessToken: string;
refreshToken: string;
expiresAt: string;
};
// 認証用のインスタンスを作成
export const authenticator = new Authenticator<SessionUser>();
// strategyの登録
authenticator.use(
new OAuth2Strategy(
{
authorizationEndpoint: "<OAUTH_END_POINT>",
tokenEndpoint: "<TOKEN_END_POIND>",
clientId: "your_app_client_id",
clientSecret: "your_app_client_secret",
redirectURI: "redirect_uri",
},
async ({ tokens }) => {
// ユーザ情報を取得
const userResponse = await fetch(
"<GET_USER_END_POIND>",
{
headers: {
Authorization: `Bearer ${tokens.accessToken()}`,
},
},
);
if (!userResponse.ok) {
throw new Error("Failed to fetch user data");
}
const user: SessionUser = await userResponse.json();
// 有効期限を計算しておく (現在時刻 + expires_in秒)
// この期限を見て、トークンをリフレッシュするか判断
const expiresIn =
(tokens.accessTokenExpiresInSeconds() as number) || 3600;
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
// トークン情報をユーザーオブジェクトに含めて返す
return {
...user,
accessToken: tokens.accessToken() || "",
refreshToken: tokens.refreshToken() || "",
expiresAt,
};
},
),
// 任意だが、複数ストラテジーを登録する場合は必要
"provider_name",
);

login.tsxでこう
login.tsx
export const action = async ({ request }: Route.ActionArgs) => {
try {
// 認証
return await authenticator.authenticate("provide_name", request);
} catch (error) {
if (error instanceof Response) {
throw error;
}
logger.error(error);
return {
error: "認証エラーが発生しました。",
};
}
};
export const loader = async ({ request }: Route.LoaderArgs) => {
const session = await getSession(request.headers.get("Cookie"));
if (session.has("user")) {
return redirect("/");
}
return null;
};

リダイレクト先でこう
callback.tsx
import { authenticator, saveSession } from "@/.server/auth";
import { redirect } from "react-router";
import type { Route } from "./+types/callback.ts";
export async function loader({ request }: Route.LoaderArgs) {
const user = await authenticator.authenticate("provider_name", request);
// セッションを保存
const headers = await saveSession(request, user);
// Set-Cookieをつけて"/"にリダイレクト
return redirect("/", { headers });
}
export default function CallbackPage() {
return (
<div className="flex h-screen items-center justify-center">
<p className="text-lg">認証中...</p>
</div>
);
}

remix-auth-oauth2の中でURLにstate
クエリがあれば、設定したredirect_uri
にリダイレクト、なければcode
確認してアクセストークンを発行っていう処理があるので、callback先でリダイレクトループが発生しない。

結論
OAuthだけなら、Remix-Auth使わなくてもいいかな。使ってもいいかな。どっちでもいいなって感じ。
このスクラップは2025/03/01にクローズされました