Closed8

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

omihirofumiomihirofumi

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

omihirofumiomihirofumi

こんな感じで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", 
);
omihirofumiomihirofumi

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;
};

omihirofumiomihirofumi

リダイレクト先でこう

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>
  );
}
omihirofumiomihirofumi

結論

OAuthだけなら、Remix-Auth使わなくてもいいかな。使ってもいいかな。どっちでもいいなって感じ。

このスクラップは2025/03/01にクローズされました