使用 NextAuth.js 实现用户认证和授权
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) return null
// 验证用户凭据的逻辑
const user = await verifyUser(credentials.email, credentials.password)
return user || null
}
})
],
session: {
strategy: 'jwt'
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role
}
return token
},
async session({ session, token }) {
if (token) {
session.user.id = token.sub
session.user.role = token.role
}
return session
}
},
pages: {
signIn: '/auth/signin',
signUp: '/auth/signup'
}
})
import { handlers } from '@/lib/auth'
export const { GET, POST } = handlers
# 环境变量
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
http://localhost:3000/api/auth/callback/github
# 环境变量
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
http://localhost:3000/api/auth/callback/google
import bcrypt from 'bcryptjs'
import { prisma } from '@/lib/prisma'
export async function verifyUser(email: string, password: string) {
const user = await prisma.user.findUnique({
where: { email }
})
if (!user || !user.password) return null
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) return null
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role
}
}
export async function createUser(email: string, password: string, name: string) {
const hashedPassword = await bcrypt.hash(password, 12)
return await prisma.user.create({
data: {
email,
password: hashedPassword,
name
}
})
}
import { useSession } from 'next-auth/react'
export function UserProfile() {
const { data: session, status } = useSession()
if (status === 'loading') return <div>加载中...</div>
if (status === 'unauthenticated') return <div>未登录</div>
return (
<div>
<p>欢迎,{session?.user?.name}</p>
<p>邮箱:{session?.user?.email}</p>
</div>
)
}
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/auth/signin')
}
return (
<div>
<h1>欢迎,{session.user?.name}</h1>
</div>
)
}
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export async function ProtectedPage({
children,
requiredRole = 'USER'
}: {
children: React.ReactNode
requiredRole?: 'USER' | 'ADMIN'
}) {
const session = await auth()
if (!session) {
redirect('/auth/signin')
}
if (requiredRole === 'ADMIN' && session.user?.role !== 'ADMIN') {
redirect('/unauthorized')
}
return <>{children}</>
}
import { auth } from '@/lib/auth'
import { NextRequest, NextResponse } from 'next/server'
export async function withAuth(handler: Function) {
return async (request: NextRequest, context: any) => {
const session = await auth()
if (!session) {
return NextResponse.json(
{ error: '未授权访问' },
{ status: 401 }
)
}
// 将用户信息添加到请求上下文
context.user = session.user
return handler(request, context)
}
}
'use client'
import { useSession } from 'next-auth/react'
interface RoleGuardProps {
allowedRoles: string[]
children: React.ReactNode
fallback?: React.ReactNode
}
export function RoleGuard({ allowedRoles, children, fallback }: RoleGuardProps) {
const { data: session } = useSession()
if (!session?.user?.role || !allowedRoles.includes(session.user.role)) {
return fallback || <div>权限不足</div>
}
return <>{children}</>
}
import { auth } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
export async function PUT(request: Request) {
const session = await auth()
if (!session) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
const { name, avatar } = await request.json()
const user = await prisma.user.update({
where: { id: session.user.id },
data: { name, avatar }
})
return NextResponse.json({ user })
}
'use client'
import { useState } from 'react'
import { useSession } from 'next-auth/react'
export function ProfileForm() {
const { data: session, update } = useSession()
const [name, setName] = useState(session?.user?.name || '')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const response = await fetch('/api/user/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
})
if (response.ok) {
// 更新客户端会话
await update({ name })
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="姓名"
/>
<button type="submit">保存</button>
</form>
)
}
'use client'
import { signOut } from 'next-auth/react'
export function LogoutButton() {
return (
<button
onClick={() => signOut({ callbackUrl: '/' })}
className="text-red-600 hover:text-red-800"
>
登出
</button>
)
}
import { signOut } from '@/lib/auth'
export async function POST() {
await signOut({ redirectTo: '/' })
}
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'
export default auth((req) => {
const { pathname } = req.nextUrl
// 保护的路由
if (pathname.startsWith('/dashboard')) {
if (!req.auth) {
return NextResponse.redirect(new URL('/auth/signin', req.url))
}
}
// 管理员路由
if (pathname.startsWith('/admin')) {
if (!req.auth || req.auth.user?.role !== 'ADMIN') {
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
}
return NextResponse.next()
})
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
'use client'
import { SessionProvider } from 'next-auth/react'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
{children}
</SessionProvider>
)
}