- Published on
如何使用 NextAuth.js 实现 Nextjs 的身份验证
引言
NextAuth.js 是一个用于 Next.js 的身份验证库。本文将以Nextjs App Router 为例,介绍如何使用 NextAuth.js 实现 Nextjs的身份验证。以及过程中遇到的问题和解决方案。 本文使用的版本分别为:
"next": "15.2.1",
"next-auth": "^4.24.11"
安装及配置
基本的安装及配置本文不做介绍,请参考NextAuth.js 官方文档。如果是使用 Nextjs App Router ,请参考NextAuth.js App Router Guide。
核心的目录路径为:/app/api/auth/[...nextauth]/route.ts
,这是 NextAuth.js 的默认路由路径,不能修改。
其中代码分别如下:
// /app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import { authOptions } from "./config";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
这是 Nextjs 最新版本 App Router 的Api路由配置方式。
// /app/api/auth/config.ts
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/lib/prisma"; // 确保路径正确
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";
import type { NextAuthOptions } from "next-auth";
export interface SessionUser {
id: string;
name: string;
email: string;
image: string;
}
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
async profile(profile) {
console.log("Google Profile:", profile);
return {
id: profile.id.toString(),
name: profile.name,
email: profile.email,
image: profile.image,
};
},
}),
GithubProvider({
clientId: process.env.GITHUB_ID as string,
clientSecret: process.env.GITHUB_SECRET as string,
async profile(profile) {
console.log("GitHub Profile:", profile);
return {
id: profile.id.toString(),
name: profile.name,
email: profile.email,
image: profile.avatar_url,
};
},
}),
// 其他提供者...
],
session: {
strategy: "jwt",
// maxAge: 30 * 24 * 60 * 60, // 30 days
// updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
async session({ session, token }) {
console.log("token in callback:", token);
return { ...session, user: { ...session.user, id: token.sub } };
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
},
};
重点需要关注的为authOptions
配置,这是 NextAuth.js 的配置选项。
adapter
adapter: PrismaAdapter(prisma)
让 NextAuth 用 Prisma 读写用户、session、account 等表。具体的表结构,字段等见下文。本地开发时,如果不配置 adapter,会在内存中创建用户,登录等信息,不会写入数据库。生产环境必须配置,才能持久化。
providers
GoogleProvider
和 GithubProvider
,可以添加多个 provider,如 GoogleProvider
和 GithubProvider
。需要在对应平台注册应用,获取clientId
和clientSecret
。
clientId/clientSecret
:从环境变量读取 OAuth 配置。profile
方法:自定义 provider 返回的用户对象结构,确保有 id、name、email、image 字段。 Google: profile.image GitHub: profile.avatar_url
session
strategy: "jwt"
使用 JWT(JSON Web Token)无状态存储 session,而不是数据库 session。这里需要详细介绍一下 JWT 和 session 的概念。
session 的作用 session 是用户登录后,前端和后端用来识别用户身份的一种机制。 在 NextAuth 里,session 用于保存用户的登录状态和部分用户信息,前端每次请求时可以带上 session,后端据此判断用户是谁。
session.strategy 的选项 NextAuth 支持两种 session 策略:
jwt
:无状态,基于 JSON Web Token(JWT)database
:有状态,基于数据库 session 表
"jwt" 策略原理:
用户登录后,NextAuth 生成一个 JWT(加密的字符串),包含用户信息。 JWT 存在客户端(通常是 cookie),每次请求时带上。 服务器通过密钥验证 JWT,解析出用户信息,无需查数据库。
优点:
- 无需频繁访问数据库,性能高,扩展性好。
- 适合无服务器(serverless)架构。
- 部署简单,session 不会丢失(因为都在客户端)。
缺点:
- JWT 体积较大,不能存太多敏感信息。
- 不能主动失效(如强制登出),只能等过期。 如果密钥泄露,所有 session 都不安全。
"database" 策略原理:
用户登录后,NextAuth 在数据库 session 表里存一条记录,session id 存在 cookie。每次请求时,服务器查数据库,验证 session 是否有效。
优点:
- 可以主动失效 session(如后台踢人、登出)。
- 可以存储更多信息,安全性高。
缺点:
- 每次请求都要查数据库,性能略低。
- 需要维护 session 表,适合传统后端服务。
callbacks
callbacks 是 NextAuth 提供的钩子(hook),允许你在认证流程的关键节点自定义逻辑。 你可以用它们来修改 session、token、用户信息、跳转地址等,满足业务需求
常用的 callbacks 字段
signIn
:用户登录时触发,可以用来允许/拒绝登录。redirect
:登录/登出/认证后跳转时触发,可以自定义跳转地址。session
:每次 session 被访问时触发,可以自定义 session 返回给前端的内容。jwt
:每次生成/更新 JWT 时触发,可以自定义 token 内容。account
、user
:OAuth 登录时触发,可以自定义用户/账号信息。
上述代码中用到的session
和jwt
回调,作用分别如下:
session
回调:- 作用: 每次前端调用 getSession() 或 useSession() 时触发。 可以自定义返回给前端的 session 内容。
- 参数: session:默认的 session 对象(包含 user、expires 等)。 token:当前的 JWT token。
- 返回值: 返回新的 session 对象。 上述代码把 token.sub(用户 id)加到 session.user.id 上,方便前端获取用户 id。
jwt
回调:- 作用: 每次生成/更新 JWT 时触发。 可以自定义 token 内容(比如加上用户 id、角色等)。
- 参数: token:当前的 token。 user:登录时的用户对象(只在登录时有)。
- 返回值: 返回新的 token 对象。上述代码登录时把 user.id 存到 token.id 上,后续 session 回调可以用。
使用Session获取用户登录信息
当用户登录后,我们需要获取用户的登录信息,以便在页面中显示或做其他操作。NextAuth.js 提供了两种方式获取用户登录信息:
- 客户端,使用
useSession
钩子在React 客户端组件中获取用户登录信息。
import { useSession } from "next-auth/react";
const { data: session } = useSession();
if (session) {
console.log("Session:", session);
}
- 服务端,使用
getServerSession
函数获取用户登录信息。
import { getServerSession } from "next-auth/next"
import { authOptions } from "./auth/[...nextauth]"
export default async (req, res) => {
const session = await getServerSession(req, res, authOptions)
if (session) {
res.send({
content:
"This is protected content. You can access this content because you are signed in.",
})
} else {
res.send({
error: "You must be signed in to view the protected content on this page.",
})
}
}
详情见NextAuth.js Session中使用useSession
和getServerSession
部分。
数据库设置
NextAuth.js 支持多种数据库,如 PostgreSQL、MySQL、MongoDB 等 ,也支持多种Adapter。本文使用 PostgreSQL 作为数据库,并使用 Prisma Adapter 连接数据库。
具体的步骤见Prisma Adapter,其中数据库表结构及Prisma Schema也在文档中,直接复制到项目中即可。然后按照文档步骤进行即可,生成Prisma Client,并配置到 NextAuth.js 的配置中。
踩坑:
- 每次调用
/api/auth/xxxx
下的路由都会报404错误。经过排查发现是中间件配置有问题。
// /app/middleware.ts
export const config = {
// Matcher ignoring `/_next/` and `/api/`
// matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)", "/:path*"], 错误配置,导致 /api/auth/xxxx 被拦截
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], // 正确配置,/api/auth/xxxx 不会被拦截
};
总结
整体步骤
- 安装 NextAuth.js 及依赖
- 配置 NextAuth.js 的配置文件
- 配置 Nextjs 的 Api 路由
- 安装数据库,配置数据库连接
- 安装Prisma,配置 Prisma Schema,生成 Prisma Client
- Nextjs 配置 Prisma Adapter
- 去对应平台注册应用,获取 clientId 和 clientSecret
- 运行项目,测试登录,登出,获取用户信息等功能