NextAuth.js 认证库
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 中直接调用的会话获取函数signIn和signOut:用于 Server Actions 或 API 路由的登录/登出方法
连接 OAuth 提供商(以 GitHub 为例)
1. 注册 GitHub OAuth App
- 进入 GitHub Developer Settings
- 新建 OAuth App,填写:
- Homepage URL:
http://localhost:3000 - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
- Homepage URL:
- 创建后获得
Client ID和Client Secret
2. 将凭证写入环境变量
AUTH_GITHUB_ID=你的ClientID
AUTH_GITHUB_SECRET=你的ClientSecret
3. 在 auth.ts 中引用环境变量
NextAuth.js 会自动读取 AUTH_GITHUB_ID 和 AUTH_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 获取会话状态。
- 创建上下文提供者组件:
// components/session-provider.tsx
"use client"
import { SessionProvider } from "next-auth/react"
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
- 在根布局中引入:
// 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>
)
}
- 在任意客户端组件中使用:
"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 适配器示例
- 安装依赖:
npm install @next-auth/prisma-adapter prisma @prisma/client
npx prisma init
-
在
prisma/schema.prisma中添加 NextAuth.js 所需模型(参考官方文档)。 -
配置适配器:
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 仅包含 name、email、image。如果你在 callbacks.jwt 或 callbacks.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
},
}