NextAuth.js 认证库

FreeGuideOnline 最新 2026-07-12

bash npx create-next-app@latest my-auth-app --typescript --tailwind --eslint


---

## 安装与基础配置

### 1. 安装 NextAuth.js

NextAuth.js v5(即 `next-auth` v5)是目前推荐的最新版本,支持 React Server Components 等新特性。

```bash
npm install next-auth@beta

若使用 Pages Router 或需要稳定版,可安装 next-auth@4,但本教程以 v5 为主线。

2. 生成 Auth Secret

AUTH_SECRET 用于加密 Cookie 和 JWT,请通过以下命令生成并放入 .env.local

npx auth secret

.env.local 文件会新增:

AUTH_SECRET=你的生成密钥

切勿将此密钥提交到代码仓库。

3. 创建核心配置文件

在项目根目录创建 auth.ts(或 auth.js),它将成为整个认证系统的入口。

// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
})

此时你已经完成了最小化配置。这段代码导出了以下实用工具:

  • handlers:包含 GET 和 POST 的路由处理器,用于接管 API 路由
  • auth:可在 Server Components 中直接调用的会话获取函数
  • signInsignOut:用于 Server Actions 或 API 路由的登录/登出方法

连接 OAuth 提供商(以 GitHub 为例)

1. 注册 GitHub OAuth App

  • 进入 GitHub Developer Settings
  • 新建 OAuth App,填写:
    • Homepage URLhttp://localhost:3000
    • Authorization callback URLhttp://localhost:3000/api/auth/callback/github
  • 创建后获得 Client IDClient Secret

2. 将凭证写入环境变量

AUTH_GITHUB_ID=你的ClientID
AUTH_GITHUB_SECRET=你的ClientSecret

3. 在 auth.ts 中引用环境变量

NextAuth.js 会自动读取 AUTH_GITHUB_IDAUTH_GITHUB_SECRET,因此你无需显式传递。但仍可手动传入:

import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.AUTH_GITHUB_ID,
      clientSecret: process.env.AUTH_GITHUB_SECRET,
    }),
  ],
})

搭建 API 路由

在 App Router 中,NextAuth.js 使用一个全捕获的 API 路由来接管所有认证请求。

创建 app/api/auth/[...nextauth]/route.ts

// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers

此路由会处理以下端点:

  • /api/auth/signin:登录页面
  • /api/auth/signout:登出页面
  • /api/auth/callback/*:OAuth 回调
  • /api/auth/session:获取当前会话
  • /api/auth/csrf:CSRF token

至此,你已拥有一个可运行的 OAuth 登录系统。


使用会话:在客户端与服务器端访问用户

服务器组件中获取会话

在 App Router 的 Server Component 中,直接调用 auth() 即可获取当前用户信息(无需额外配置即可工作)。

// app/page.tsx
import { auth } from "@/auth"

export default async function Home() {
  const session = await auth()
  return (
    <main>
      {session?.user ? (
        <p>欢迎, {session.user.name}</p>
      ) : (
        <p>请登录</p>
      )}
    </main>
  )
}

客户端组件中获取会话

使用 SessionProvider 包裹客户端组件,并通过 useSession Hook 获取会话状态。

  1. 创建上下文提供者组件:
// components/session-provider.tsx
"use client"

import { SessionProvider } from "next-auth/react"

export function Providers({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>
}
  1. 在根布局中引入:
// app/layout.tsx
import { Providers } from "@/components/session-provider"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
  1. 在任意客户端组件中使用:
"use client"
import { useSession } from "next-auth/react"

export default function UserInfo() {
  const { data: session, status } = useSession()
  if (status === "loading") return <p>加载中...</p>
  return <div>{session?.user?.email}</div>
}

保护路由与中间件

仅靠前端隐藏界面并不安全,必须从服务器侧阻断未认证的访问。

方案一:单个页面内检查

在需要保护的页面顶部添加会话检查:

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

export default async function Dashboard() {
  const session = await auth()
  if (!session) redirect("/api/auth/signin")
  return <div>受保护内容</div>
}

方案二:全局中间件拦截

创建 middleware.ts 文件,可灵活指定受保护路径。

// middleware.ts
import { auth } from "@/auth"

export default auth((req) => {
  const isLoggedIn = !!req.auth
  const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard")
  
  if (isOnDashboard && !isLoggedIn) {
    return Response.redirect(new URL("/api/auth/signin", req.nextUrl))
  }
})

// 配置匹配器,避免对静态资源和 API 路由执行认证
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

方案三:针对特定路由的中间件

也可使用简写形式,直接导出 auth 作为中间件,并配置 matcher

export { auth as middleware } from "@/auth"
export const config = {
  matcher: ["/dashboard/:path*"]
}

此时任何未登录用户访问 /dashboard 下的路径,都会被重定向到登录页。


添加凭据认证(邮箱密码登录)

除了 OAuth,NextAuth.js 也内置了 Credentials 提供商,用于自定义验证逻辑(例如检查数据库中的密码)。

import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import bcrypt from "bcryptjs"

export const { handlers, auth } = NextAuth({
  providers: [
    Credentials({
      name: "credentials",
      credentials: {
        email: { label: "邮箱", type: "email" },
        password: { label: "密码", type: "password" },
      },
      async authorize(credentials) {
        // 在此处连接数据库验证用户
        const user = { id: "1", email: "test@example.com", password: "hashed" }
        
        if (credentials.email !== user.email) return null
        const isValid = await bcrypt.compare(credentials.password, user.password)
        if (!isValid) return null
        
        return { id: user.id, email: user.email }
      },
    }),
  ],
})

安全提示:凭据提供商的密码交互务必使用 HTTPS,并配合 csrf 防护。生产环境建议对 authorize 内部的错误小心处理,避免暴露过多信息。


数据库与会话持久化

默认情况下,NextAuth.js 使用 JWT 策略将会话编码在 Cookie 中,无需数据库。但若要支持复杂用户模型、关联账号或吊销会话,需接入数据库适配器。

使用 Prisma 适配器示例

  1. 安装依赖:
npm install @next-auth/prisma-adapter prisma @prisma/client
npx prisma init
  1. prisma/schema.prisma 中添加 NextAuth.js 所需模型(参考官方文档)。

  2. 配置适配器:

import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"

export const { handlers, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [ /* ... */ ],
})

之后,用户登录信息会自动写入数据库,且会话改为数据库管理,JWT 将不再被使用(除非同时显式设置 session: { strategy: "jwt" })。


自定义页面与国际化

NextAuth.js 内置了简单的登录、登出、错误页面,你可以通过配置覆盖它们。

import NextAuth from "next-auth"

export const { handlers, auth } = NextAuth({
  pages: {
    signIn: "/auth/signin",
    signOut: "/auth/signout",
    error: "/auth/error",
  },
})

可以在这些自定义页面中使用 signIn 函数和 csrfToken 构建自己的表单,实现品牌完全一致的用户体验。


进阶技巧与常见问题

类型扩展

默认的 Session.user 仅包含 nameemailimage。如果你在 callbacks.jwtcallbacks.session 中添加了自定义字段(如 role),需要类型扩展:

// types/next-auth.d.ts
import NextAuth from "next-auth"

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

  interface User {
    role: string
  }
}

处理 token 过期与刷新

对于 OAuth 提供商的 access token 过期,你可以在 jwt 回调中检测并刷新:

callbacks: {
  async jwt({ token, account }) {
    if (account) {
      token.accessToken = account.access_token
      token.expiresAt = account.expires_at
    }
    if (Date.now() < (token.expiresAt as number) * 1000) {
      return token
    }
    // 调用刷新端点获取新 token,并更新 token
    return token
  },
}