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. 01配置 Auth.js 5 与 OAuth 提供方
  2. 02使用 Middleware 保护路由
  3. 03在 Server Components 中读取会话
  4. 04验证 Server Actions 的身份
  5. 05实现基于角色的访问控制
  6. 06提供 TypeScript 类型扩展模式

适用场景

  1. 01为 Next.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 身份验证

概述

提供适用于 Next.js 15+ App Router 的 Auth.js 5(NextAuth.js)身份验证实现模式,涵盖从初始设置到生产环境就绪的基于角色的访问控制实现的完整身份验证生命周期。

适用场景

  • 从头开始设置 Auth.js 5,或添加 OAuth 提供商
  • 使用 Middleware 实现受保护路由
  • 在 Server Components 和 Server Actions 中处理身份验证
  • 实现基于角色的访问控制(RBAC)
  • 创建基于凭据或 OAuth 的登录/登出流程

说明

1. 安装依赖

为 Next.js App Router 安装 Auth.js v5(beta):

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 配置

在项目根目录创建 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. 处理登录/登出

使用 Server Action 创建登录页面:

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

输出: 仅为经过身份验证的用户创建待办事项;未授权请求会引发错误。

最佳实践

  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 - 使用 Server Actions 执行 Cookie 操作
  • 会话回调的调用时机 - 仅在创建或访问会话时调用,而非每次请求都调用
常见错误
// ❌ 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 属性

参考资料

评论

支持 Markdown