mcpField
N
nextjs-authentication

nextjs-authentication

github.com/giuseppe-trisciuoglioセキュリティ3653.8k 回インストール7 週間前に更新
リポジトリ

nextjs-authentication について

Next.js 15+ App Router で認証を実装する開発者向けの、Auth.js 5 の実装ガイドです。サインイン、ルート保護、ロールベースのアクセス制御に加え、Server Components、Server Actions、Middleware のセキュリティパターンを扱います。

主な機能

  1. 01Auth.js 5 と OAuth プロバイダーの設定
  2. 02Middleware によるルート保護
  3. 03Server Components でのセッション取得
  4. 04Server Actions での認証確認
  5. 05ロールベースのアクセス制御
  6. 06TypeScript のセッション型拡張

活用シーン

  1. 01Next.js アプリへの OAuth サインイン追加
  2. 02認証済みユーザーにダッシュボードを限定
  3. 03管理パネルのロール権限設定
  4. 04フォーム操作でのユーザー認証確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication

インストール後、Claude Code で/nextjs-authenticationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
authjs-setup.md
best-practices.md
database-adapter.md
examples.md
oauth-providers.md
testing-patterns.md

詳細

Next.js 認証

概要

Auth.js 5(NextAuth.js)を使用して、Next.js 15 以降の App Router に認証を実装するパターンを提供します。初期設定から、本番環境に対応したロールベースのアクセス制御の実装まで、認証ライフサイクル全体を網羅しています。

使用する場面

  • Auth.js 5 を新規に設定する、または OAuth プロバイダーを追加する場合
  • Middleware で保護されたルートを実装する場合
  • Server Components や Server Actions で認証を処理する場合
  • ロールベースのアクセス制御(RBAC)を実装する場合
  • 認証情報ベースまたは OAuth によるサインイン/サインアウトのフローを作成する場合

手順

1. 依存関係をインストールする

Next.js App Router 用に Auth.js v5(ベータ版)をインストールします。

npm install next-auth@beta
2. 環境変数を設定する

必要な変数を設定した .env.local を作成します。

# Required for Auth.js
AUTH_SECRET="your-secret-key-here"
AUTH_URL="http://localhost:3000"

# OAuth Providers (add as needed)
GITHUB_ID="your-github-client-id"
GITHUB_SECRET="your-github-client-secret"
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"

次のコマンドで AUTH_SECRET を生成します。

openssl rand -base64 32
3. 認証設定を作成する

プロバイダーとコールバックを設定した auth.ts をプロジェクトのルートに作成します。

import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";

export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string;
      }
      return session;
    },
  },
  pages: {
    signIn: "/login",
    error: "/error",
  },
});
4. API ルートハンドラーを作成する

app/api/auth/[...nextauth]/route.ts を作成します。

export { GET, POST } from "@/auth";
5. ルート保護用の Middleware を追加する

プロジェクトのルートに middleware.ts を作成します。

import { auth } from "@/auth";
import { NextResponse } from "next/server";

export default auth((req) => {
  const { nextUrl } = req;
  const isLoggedIn = !!req.auth;
  const isApiAuthRoute = nextUrl.pathname.startsWith("/api/auth");
  const isPublicRoute = ["/", "/login", "/register"].includes(nextUrl.pathname);
  const isProtectedRoute = nextUrl.pathname.startsWith("/dashboard");

  if (isApiAuthRoute) return NextResponse.next();

  if (!isLoggedIn && isProtectedRoute) {
    return NextResponse.redirect(new URL("/login", nextUrl));
  }

  if (isLoggedIn && nextUrl.pathname === "/login") {
    return NextResponse.redirect(new URL("/dashboard", nextUrl));
  }

  return NextResponse.next();
});

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)"],
};
6. Server Components でセッションにアクセスする

auth() 関数を使用して、Server Components でセッションにアクセスします。

import { auth } from "@/auth";
import { redirect } from "next/navigation";

export default async function DashboardPage() {
  const session = await auth();

  if (!session) {
    redirect("/login");
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}</h1>
    </div>
  );
}
7. Server Actions を保護する

ミューテーションを実行する前に、必ず Server Actions で認証を確認します。

"use server";

import { auth } from "@/auth";

export async function createTodo(formData: FormData) {
  const session = await auth();

  if (!session?.user) {
    throw new Error("Unauthorized");
  }

  // Proceed with protected action
  const title = formData.get("title") as string;
  await db.todo.create({
    data: { title, userId: session.user.id },
  });
}
8. サインイン/サインアウトを処理する

サーバーアクションを使ったログインページを作成します。

// app/login/page.tsx
import { signIn } from "@/auth";
import { redirect } from "next/navigation";

export default function LoginPage() {
  async function handleLogin(formData: FormData) {
    "use server";

    const result = await signIn("credentials", {
      email: formData.get("email"),
      password: formData.get("password"),
      redirect: false,
    });

    if (result?.error) {
      return { error: "Invalid credentials" };
    }

    redirect("/dashboard");
  }

  return (
    <form action={handleLogin}>
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Password" required />
      <button type="submit">Sign In</button>
    </form>
  );
}

クライアント側のサインアウトの場合:

"use client";

import { signOut } from "next-auth/react";

export function SignOutButton() {
  return <button onClick={() => signOut()}>Sign Out</button>;
}
9. ロールベースのアクセス制御を実装する

Server Components でロールを確認します。

import { auth } from "@/auth";
import { unauthorized } from "next/navigation";

export default async function AdminPage() {
  const session = await auth();

  if (session?.user?.role !== "admin") {
    unauthorized();
  }

  return <AdminDashboard />;
}
10. TypeScript の型を拡張する

型安全なセッションを実現するため、types/next-auth.d.ts を作成します。

import { DefaultSession } from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
      role: "user" | "admin";
    } & DefaultSession["user"];
  }

  interface User {
    role?: "user" | "admin";
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    id?: string;
    role?: "user" | "admin";
  }
}

例

例 1:保護されたダッシュボードの実装

入力: 認証済みユーザーだけがアクセスできるダッシュボードが必要です。

実装:

// app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
import { getUserTodos } from "@/app/lib/data";

export default async function DashboardPage() {
  const session = await auth();

  if (!session?.user?.id) {
    redirect("/login");
  }

  const todos = await getUserTodos(session.user.id);

  return (
    <main>
      <h1>Welcome, {session.user.name}</h1>
      <p>Email: {session.user.email}</p>
      <TodoList todos={todos} />
    </main>
  );
}

出力: ダッシュボードは認証済みユーザーにのみ表示され、各ユーザー固有のデータが表示されます。

例 2:ロールベースの管理者パネル

入力: 「admin」ロールを持つユーザーだけが管理者パネルにアクセスできるようにします。

実装:

// app/admin/page.tsx
import { auth } from "@/auth";
import { unauthorized } from "next/navigation";

export default async function AdminPage() {
  const session = await auth();

  if (session?.user?.role !== "admin") {
    unauthorized();
  }

  return (
    <main>
      <h1>Admin Panel</h1>
      <p>Welcome, administrator {session.user.name}</p>
    </main>
  );
}

出力: 管理者ユーザーにのみパネルが表示され、それ以外のユーザーには 401 エラーが返されます。

例 3:フォームを使った安全な Server Action

入力: 認証済みユーザーだけがフォームを送信できるようにします。

実装:

// app/components/create-todo-form.tsx
"use server";

import { auth } from "@/auth";
import { revalidatePath } from "next/cache";

export async function createTodo(formData: FormData) {
  const session = await auth();

  if (!session?.user?.id) {
    throw new Error("Unauthorized");
  }

  const title = formData.get("title") as string;

  await db.todo.create({
    data: {
      title,
      userId: session.user.id,
    },
  });

  revalidatePath("/dashboard");
}

// Usage in component
export function CreateTodoForm() {
  return (
    <form action={createTodo}>
      <input name="title" placeholder="New todo..." required />
      <button type="submit">Add Todo</button>
    </form>
  );
}

出力: Todo は認証済みユーザーに対してのみ作成され、認証されていないリクエストではエラーがスローされます。

ベストプラクティス

  1. デフォルトでは Server Components を使う - クライアント側の JavaScript を使わずにセッションへ直接アクセスします
  2. Client Components を最小限にする - リアクティブなセッション更新が必要な場合にのみ useSession() を使います
  3. セッション確認をキャッシュする - 同じレンダリング内で繰り返し参照する場合は、React の cache() を使います
  4. Middleware は楽観的な確認に使う - すばやくリダイレクトしつつ、Server Actions でも必ず再確認します
  5. Server Actions を API エンドポイントとして扱う - ミューテーションの前に必ず認証します
  6. シークレットをハードコードしない - すべての認証情報に環境変数を使用します
  7. 適切なエラー処理を実装する - 適切な HTTP ステータスコードを返します
  8. TypeScript の型拡張を使う - カスタムフィールド用に NextAuth の型を拡張します
  9. 認証ロジックを分離する - 一貫した確認を行うため、DAL(データアクセス層)を作成します
  10. 認証フローをテストする - ユニットテストで auth() 関数をモックします

制約と注意事項

重大な制限事項
  • Middleware は Edge ランタイムで実行されます - データベースドライバーなどの Node.js API は使用できません
  • Server Components では Cookie を設定できません - Cookie の操作には Server Actions を使います
  • セッションコールバックの実行タイミング - セッションの作成時またはアクセス時にのみ呼び出され、リクエストごとには呼び出されません
よくある間違い
// ❌ WRONG: Setting cookies in Server Component
export default async function Page() {
  cookies().set("key", "value"); // Won't work
}

// ✅ CORRECT: Use Server Action
async function setCookieAction() {
  "use server";
  cookies().set("key", "value");
}
// ❌ WRONG: Database queries in Middleware
export default auth(async (req) => {
  const user = await db.user.findUnique(); // Won't work in Edge
});

// ✅ CORRECT: Use only Edge-compatible APIs
export default auth(async (req) => {
  const session = req.auth; // This works
});
セキュリティに関する考慮事項
  • Server Actions では必ず認証を確認します。Middleware だけでは不十分です
  • 未認証のアクセスには unauthorized() を使い、それ以外の場合は redirect() を使います
  • 機密性の高いトークンは httpOnly Cookie に保存します
  • 処理する前に、すべてのユーザー入力を検証します
  • 本番環境では HTTPS を使用します
  • 適切な Cookie の sameSite 属性を設定します

参考資料

コメント