概述
本文档介绍了如何使用 Next-Auth 配置一个同时支持普通用户和管理员用户登录的认证系统。
基本配置
首先,我们需要设置 Next-Auth 的基本配置,包括提供者、回调函数和页面路由。
import type { NextAuthConfig } from 'next-auth'
import type { User } from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import { LoginSchema } from '@/types/auth'
import { prisma } from '@/lib/prisma'
import bcrypt from 'bcryptjs'
export const authOptions = {
providers: [
// 配置提供者
],
callbacks: {
// 配置回调函数
},
pages: {
// 配置页面路由
},
} satisfies NextAuthConfig
配置认证提供者
需要配置两个 Credentials 提供者,分别用于普通用户和管理员用户的登录。
providers: [
Credentials({
id: 'user-login', // 普通用户登录
name: 'User Credentials',
async authorize(credentials) {
try {
const validatedFields = LoginSchema.safeParse(credentials)
if (!validatedFields.success) return null
const { email, password } = validatedFields.data
const user = await prisma.user.findUnique({
where: { email },
select: {
id: true,
email: true,
password: true,
username: true,
avatar_url: true,
},
})
if (!user?.password) return null
const passwordsMatch = await bcrypt.compare(password, user.password)
if (!passwordsMatch) return null
return {
id: user.id,
email: user.email,
username: user.username || user.email?.split('@')[0] || '',
avatar_url: user.avatar_url || '',
role: 'user', // 添加角色标识
}
} catch (error) {
console.error('User authorization error:', error)
return null
}
},
}),
Credentials({
id: 'admin-login', // 管理员登录
name: 'Admin Credentials',
async authorize(credentials) {
try {
const validatedFields = LoginSchema.safeParse(credentials)
if (!validatedFields.success) return null
const { email, password } = validatedFields.data
const admin = await prisma.adminUser.findUnique({
where: { email },
select: {
id: true,
email: true,
password: true,
username: true,
avatar_url: true,
role: true,
},
})
if (!admin?.password) return null
const passwordsMatch = await bcrypt.compare(password, admin.password)
if (!passwordsMatch) return null
return {
id: admin.id,
email: admin.email,
username: admin.username,
avatar_url: admin.avatar_url || '',
role: 'admin', // 添加角色标识
}
} catch (error) {
console.error('Admin authorization error:', error)
return null
}
},
}),
],
配置回调函数
回调函数用于处理会话和 JWT 令牌的自定义逻辑。
callbacks: {
async session({ session, token }) {
if (session.user) {
session.user.username = token.username as string
session.user.id = token.sub as string
session.user.avatar_url = token.avatar_url as string
session.user.role = token.role as string // 添加角色
}
return session
},
async jwt({ token, user }) {
if (user) {
token.username = user.username
token.sub = user.id
token.avatar_url = user.avatar_url
token.role = user.role // 添加角色
}
return token
},
},
配置页面路由
配置自定义的登录页面和错误页面。
pages: {
signIn: '/login', // 普通用户登录页
error: '/auth/error', // 错误页面
},
扩展 Session 类型
为了在 TypeScript 中正确识别自定义的会话属性,我们需要扩展 Next-Auth 的类型定义。
// types/next-auth.d.ts
import { DefaultSession } from "next-auth"
declare module "next-auth" {
interface Session {
user: {
id: string
username: string
avatar_url: string
role: string
} & DefaultSession["user"]
}
interface User {
id: string
username: string
avatar_url: string
role: string
}
}
区分用户和管理员
如果需要在会话中明确区分用户和管理员,可以修改 session 回调函数:
async session({ session, token }) {
if (session.user) {
session.user.username = token.username as string
session.user.id = token.sub as string
session.user.avatar_url = token.avatar_url as string
session.user.role = token.role as string
}
// 添加区分的用户和管理员对象
if (token.role === 'admin') {
session.admin = {
id: token.sub as string,
email: session.user?.email,
username: token.username as string,
avatar_url: token.avatar_url as string,
role: 'admin'
}
} else {
session.admin = null // 如果不是管理员,设置为 null
}
return session
},
同时更新类型定义:
// types/next-auth.d.ts
interface Session {
user: {
id: string
username: string
avatar_url: string
role: string
} & DefaultSession["user"]
admin: {
id: string
email?: string | null
username: string
avatar_url: string
role: string
} | null
}
使用方式
在组件中使用会话信息:
import { useSession } from "next-auth/react"
function MyComponent() {
const { data: session } = useSession()
// 检查是否是管理员
if (session?.user?.role === 'admin') {
// 管理员逻辑
} else {
// 普通用户逻辑
}
// 或者使用扩展的 admin 对象
if (session?.admin) {
// 管理员逻辑
} else {
// 普通用户逻辑
}
}
登录方式
在登录表单中,可以通过指定 providerId 来选择使用哪个认证提供者:
import { signIn } from "next-auth/react"
// 普通用户登录
signIn("user-login", {
email,
password,
redirect: true,
callbackUrl: "/dashboard"
})
// 管理员登录
signIn("admin-login", {
email,
password,
redirect: true,
callbackUrl: "/admin/dashboard"
})