Skip to content

認証エラーハンドリング改善: tryGetAuthSession + cache() + Link→a変更 #107

@konokenj

Description

@konokenj

問題

現在の auth.ts と認証関連コンポーネントに以下の問題がある。

1. 認証APIルートへの <Link> 使用によるCORSエラーの可能性

Header.tsxsign-in/page.tsx で認証APIルート(/api/auth/sign-out, /api/auth/sign-in)に <Link prefetch={false}> を使用している。

prefetch={false} はプリフェッチを無効にするが、クリック時のクライアントサイドナビゲーション(RSCペイロードのfetch)は発動する。認証APIルートはCognitoへの302リダイレクトを返すため、fetchがクロスオリジンのCognitoドメインを追跡し、CORSエラーが発生しうる。

// 現在
<Link href="/api/auth/sign-out" prefetch={false}>Sign Out</Link>

// 修正: 通常のブラウザナビゲーションで302リダイレクトを正しく処理
<a href="/api/auth/sign-out">Sign Out</a>

sign-in/page.tsx も同様に <a> タグに変更する。

2. getSession() がトークン期限切れ時に例外を投げ、認証エラーとサーバーエラーを区別できない

現在の getSession() は認証セッション取得とDBアクセスが一体化しており:

  • トークン期限切れ時に例外が投げられ、呼び出し側で認証エラー(401相当)とサーバーエラー(500相当)を区別できない
  • API RouteでuserIdのみ必要な場合でもDBアクセスが発生する
  • cache() 未使用のため、同一リクエスト内で複数回呼ばれると毎回Cognito + DBにアクセスする

以下の3関数に分離することを提案する:

// webapp/src/lib/auth/session.ts
import { cache } from 'react';
import { cookies } from 'next/headers';
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { runWithAmplifyServerContext } from '@/lib/amplifyServerUtils';
import { prisma } from '@/lib/prisma';

// DBアクセスなし。userIdのみ必要な場合
export const getAuthSession = cache(async () => {
  const session = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: (contextSpec) => fetchAuthSession(contextSpec),
  });
  if (!session.userSub || !session.tokens?.idToken || !session.tokens?.accessToken) {
    throw new Error('session not found');
  }
  return {
    userId: session.userSub,
    email: session.tokens.idToken.payload.email as string,
    accessToken: session.tokens.accessToken.toString(),
  };
});

// 失敗時null返却。API Routeで401を返したい場合
export async function tryGetAuthSession() {
  try {
    return await getAuthSession();
  } catch {
    return null;
  }
}

// DB + 認証。User情報が必要な場合
export const getSessionWithUser = cache(async () => {
  const auth = await getAuthSession();
  const user = await prisma.user.findUnique({ where: { id: auth.userId } });
  if (!user) throw new Error(`User not found: ${auth.userId}`);
  return { ...auth, user };
});

3. safe-action.tsauthActionClient も統一すべき

現在の authActionClientgetCurrentUser() で認証しており、auth.tsgetSession() とは別の認証パスになっている。上記の getAuthSession() に統一することで、認証ロジックの重複を解消できる。

検証方法

  • トークン期限切れ時にAPI Routeが500ではなく401を返すこと
  • DB接続エラー時にサインインリダイレクトしないこと(認証エラーとサーバーエラーの区別)
  • cache() により同一リクエスト内の重複呼び出しが防止されること
  • 認証APIルート(/api/auth/*)が <a> タグで遷移され、CORSエラーが発生しないこと

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions