名称: nextauth 描述: NextAuth.js (Auth.js) 配置,包括身份提供者、适配器、会话管理、回调和 JWT 处理。 允许的工具: 读取、写入、编辑、Bash、Glob、Grep
NextAuth 技能
为使用 NextAuth.js (Auth.js) 在 Next.js 应用程序中实现身份验证提供专家级协助。
能力
- 配置 OAuth 身份提供者(谷歌、GitHub 等)
- 设置基于凭据的身份验证
- 实现数据库适配器(Prisma、Drizzle)
- 处理 JWT 和会话回调
- 配置受保护的路由和中间件
- 实现基于角色的访问控制
使用场景
当您需要时,请调用此技能:
- 为 Next.js 应用添加身份验证
- 配置 OAuth 身份提供者
- 设置数据库会话
- 实现身份验证中间件
- 处理用户角色和权限
输入参数
| 参数 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| providers | 数组 | 是 | 要配置的身份验证提供者 |
| adapter | 字符串 | 否 | 数据库适配器(prisma, drizzle) |
| sessionStrategy | 字符串 | 否 | jwt 或 database |
| callbacks | 数组 | 否 | 需要的自定义回调 |
配置示例
{
"providers": ["google", "github", "credentials"],
"adapter": "prisma",
"sessionStrategy": "jwt",
"callbacks": ["jwt", "session", "signIn"]
}
实现模式
基础配置(App Router)
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
// lib/auth.ts
import { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/lib/prisma';
import bcrypt from 'bcryptjs';
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: '邮箱', type: 'email' },
password: { label: '密码', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('无效的凭据');
}
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user || !user.hashedPassword) {
throw new Error('无效的凭据');
}
const isValid = await bcrypt.compare(
credentials.password,
user.hashedPassword
);
if (!isValid) {
throw new Error('无效的凭据');
}
return user;
},
}),
],
session: {
strategy: 'jwt',
},
pages: {
signIn: '/login',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
async signIn({ user, account, profile }) {
// 自定义登录逻辑
return true;
},
},
};
类型扩展
// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from 'next-auth';
import { JWT, DefaultJWT } from 'next-auth/jwt';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: string;
} & DefaultSession['user'];
}
interface User extends DefaultUser {
role: string;
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
id: string;
role: string;
}
}
身份验证中间件
// middleware.ts
import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';
export default withAuth(
function middleware(req) {
const token = req.nextauth.token;
const isAdmin = token?.role === 'admin';
const isAdminRoute = req.nextUrl.pathname.startsWith('/admin');
if (isAdminRoute && !isAdmin) {
return NextResponse.redirect(new URL('/unauthorized', req.url));
}
return NextResponse.next();
},
{
callbacks: {
authorized: ({ token }) => !!token,
},
}
);
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*', '/api/protected/:path*'],
};
服务端身份验证检查
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/login');
}
return (
<div>
<h1>欢迎,{session.user.name}</h1>
<p>角色: {session.user.role}</p>
</div>
);
}
客户端身份验证钩子
// components/user-menu.tsx
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export function UserMenu() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>加载中...</div>;
}
if (!session) {
return (
<button onClick={() => signIn()}>
登录
</button>
);
}
return (
<div>
<img src={session.user.image} alt={session.user.name} />
<span>{session.user.name}</span>
<button onClick={() => signOut()}>
退出登录
</button>
</div>
);
}
会话提供者
// app/providers.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 './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Prisma 数据模型
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
hashedPassword String?
role String @default("user")
accounts Account[]
sessions Session[]
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
最佳实践
- 在无服务器部署中使用 JWT 策略
- 使用必要的用户数据扩展会话
- 实现适当的 CSRF 保护
- 使用中间件进行路由保护
- 将密钥存储在环境变量中
目标流程
- nextjs-全栈开发
- oauth-社交登录
- jwt-身份验证
- rbac-实现