Admin客户端
Authentication System Guide
User Authentication and Session Management
Better Auth · Frontend Authentication · Backend Authentication
🎯 Overview
NextJS Base uses Better Auth as the authentication solution, supporting email/password login, OAuth login, and other authentication methods.
Tech Stack
| Component | Description |
|---|---|
| Better Auth | Authentication framework |
| Prisma Adapter | Database adapter |
| JWT | Token management |
| Cookies | Session storage |
🔐 Better Auth
Configuration File
// lib/auth/auth.js
import { betterAuth } from 'better-auth'
import { prismaAdapter } from 'better-auth/adapters/prisma'
import { prisma } from '@/lib/database/prisma'
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: 'postgresql',
}),
emailAndPassword: {
enabled: true,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // Update daily
},
// OAuth Configuration (Optional)
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
},
})Environment Variables
# Better Auth
BETTER_AUTH_SECRET="your-secret-key-at-least-32-characters"
BETTER_AUTH_URL="http://localhost:3000"
# OAuth (Optional)
GOOGLE_CLIENT_ID="xxx"
GOOGLE_CLIENT_SECRET="xxx"
GITHUB_CLIENT_ID="xxx"
GITHUB_CLIENT_SECRET="xxx"🌐 Frontend Authentication
Client Configuration
// lib/auth/auth-client.js
import { createAuthClient } from 'better-auth/react'
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL,
})
export const {
signIn,
signUp,
signOut,
useSession,
} = authClientLogin Page
'use client'
import { useState } from 'react'
import { signIn } from '@/lib/auth/auth-client'
import { useRouter } from 'next/navigation'
export default function LoginPage() {
const router = useRouter()
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
setError('')
const formData = new FormData(e.target)
const email = formData.get('email')
const password = formData.get('password')
try {
const result = await signIn.email({
email,
password,
})
if (result.error) {
setError(result.error.message)
} else {
router.push('/admin')
}
} catch (err) {
setError('Login failed, please try again')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
{error && <p className="error">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
</form>
)
}Register Page
'use client'
import { signUp } from '@/lib/auth/auth-client'
export default function RegisterPage() {
const handleSubmit = async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const result = await signUp.email({
email: formData.get('email'),
password: formData.get('password'),
name: formData.get('name'),
})
if (result.error) {
// Handle error
} else {
// Registration successful, redirect to login or auto login
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Username" required />
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Register</button>
</form>
)
}Get Session
'use client'
import { useSession } from '@/lib/auth/auth-client'
export default function ProfilePage() {
const { data: session, isPending } = useSession()
if (isPending) {
return <div>Loading...</div>
}
if (!session) {
return <div>Please login first</div>
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Email: {session.user.email}</p>
</div>
)
}Sign Out
import { signOut } from '@/lib/auth/auth-client'
const handleLogout = async () => {
await signOut()
// Redirect to home or login page
}🔒 Backend Authentication
Server-Side Get Session
// lib/auth/auth.js
import { auth as betterAuth } from './auth'
import { headers } from 'next/headers'
export const auth = async () => {
const session = await betterAuth.api.getSession({
headers: await headers(),
})
return session
}Use in Server Action
'use server'
import { auth } from '@/lib/auth/auth'
export async function getUserProfileAction() {
const session = await auth()
if (!session?.user) {
return { success: false, error: 'Please login first' }
}
// Get user information
const user = await prisma.user.findUnique({
where: { id: session.user.id },
})
return { success: true, data: user }
}Backend Access Control
// lib/auth/admin-auth.js
import { auth } from './auth'
import { prisma } from '@/lib/database/prisma'
export async function checkBackendAccess() {
const session = await auth()
if (!session?.user) {
throw new Error('Please login first')
}
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { hasBackendAccess: true },
})
if (!user?.hasBackendAccess) {
throw new Error('No backend access permission')
}
return session.user
}Automatic Check in wrapAction
// wrapAction automatically handles authentication
export const sysGetUserListAction = wrapAction(
'sysGetUserList', // sys prefix automatically checks backend permission
async (params, ctx) => {
// ctx.userId - Current user ID
// ctx.user - Current user information
// ctx.isAdmin - Whether admin
return await dao.getList(params)
}
)🛡️ Route Protection
Middleware Protection
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const { pathname } = request.nextUrl
// Backend route protection
if (pathname.startsWith('/admin')) {
const session = request.cookies.get('better-auth.session_token')
if (!session) {
return NextResponse.redirect(new URL('/auth/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*'],
}Page-Level Protection
// app/(admin)/admin/layout.js
import { auth } from '@/lib/auth/auth'
import { redirect } from 'next/navigation'
export default async function AdminLayout({ children }) {
const session = await auth()
if (!session?.user) {
redirect('/auth/login')
}
// Check backend access permission
const user = await prisma.user.findUnique({
where: { id: session.user.id },
})
if (!user?.hasBackendAccess) {
redirect('/403')
}
return <>{children}</>
}📊 User Model
Prisma Schema
model User {
id String @id @default(cuid())
email String @unique
emailVerified Boolean @default(false)
name String?
image String?
// Roles and permissions
roles String[] @default([])
hasBackendAccess Boolean @default(false)
// Status
banned Boolean @default(false)
banReason String?
banExpires DateTime?
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
sessions Session[]
accounts Account[]
}
model Session {
id String @id @default(cuid())
userId String
token String @unique
expiresAt DateTime
ipAddress String?
userAgent String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Account {
id String @id @default(cuid())
userId String
accountId String
providerId String
accessToken String?
refreshToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, accountId])
}