nextauth-authentication
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNextAuth Authentication
NextAuth 身份验证
You are an expert in NextAuth.js (Auth.js v5) authentication implementation. Follow these guidelines when integrating authentication in Next.js applications.
您是NextAuth.js(Auth.js v5)身份验证实现方面的专家。在Next.js应用中集成身份验证时,请遵循以下指南。
Core Principles
核心原则
- Use Auth.js v5 patterns and the universal function
auth() - Implement proper session management strategy based on your needs
- Always validate sessions server-side for sensitive operations
- Configure environment variables correctly with the prefix
AUTH_
- 使用Auth.js v5模式和通用的函数
auth() - 根据需求实施合适的会话管理策略
- 对于敏感操作,始终在服务器端验证会话
- 使用前缀正确配置环境变量
AUTH_
Installation
安装
bash
npm install next-auth@betabash
npm install next-auth@betaEnvironment Variables
环境变量
bash
undefinedbash
undefinedRequired
必填项
AUTH_SECRET=your-32-byte-secret-here # Generate with: openssl rand -base64 32
AUTH_SECRET=your-32-byte-secret-here # 生成方式:openssl rand -base64 32
Provider credentials (auto-detected if named correctly)
服务商凭证(命名正确时会自动检测)
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
Optional: Custom URL (auto-detected in most environments)
可选:自定义URL(多数环境下会自动检测)
AUTH_URL=https://your-domain.com
undefinedAUTH_URL=https://your-domain.com
undefinedBasic Configuration
基础配置
auth.ts (Root Configuration)
auth.ts(根配置)
typescript
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, // Credentials auto-detected from AUTH_GITHUB_ID/SECRET
Google,
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
// Validate credentials
const user = await validateCredentials(credentials);
if (!user) return null;
return user;
},
}),
],
session: {
strategy: 'jwt', // or 'database'
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/auth/signin',
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 (token) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
async authorized({ auth, request }) {
const isAuthenticated = !!auth?.user;
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedRoute && !isAuthenticated) {
return false; // Redirect to sign-in
}
return true;
},
},
});typescript
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, // 凭证会从AUTH_GITHUB_ID/SECRET自动检测
Google,
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
// 验证凭证
const user = await validateCredentials(credentials);
if (!user) return null;
return user;
},
}),
],
session: {
strategy: 'jwt', // 或 'database'
maxAge: 30 * 24 * 60 * 60, // 30天
},
pages: {
signIn: '/auth/signin',
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 (token) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
async authorized({ auth, request }) {
const isAuthenticated = !!auth?.user;
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedRoute && !isAuthenticated) {
return false; // 重定向到登录页
}
return true;
},
},
});Route Handlers (app/api/auth/[...nextauth]/route.ts)
路由处理器(app/api/auth/[...nextauth]/route.ts)
typescript
import { handlers } from '@/auth';
export const { GET, POST } = handlers;typescript
import { handlers } from '@/auth';
export const { GET, POST } = handlers;Middleware (middleware.ts)
中间件(middleware.ts)
typescript
import { auth } from '@/auth';
export default auth((req) => {
const isAuthenticated = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');
// Redirect authenticated users away from auth pages
if (isAuthenticated && isAuthPage) {
return Response.redirect(new URL('/dashboard', req.nextUrl));
}
// Redirect unauthenticated users from protected routes
if (!isAuthenticated && isProtectedRoute) {
return Response.redirect(new URL('/auth/signin', req.nextUrl));
}
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};typescript
import { auth } from '@/auth';
export default auth((req) => {
const isAuthenticated = !!req.auth;
const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
const isProtectedRoute = req.nextUrl.pathname.startsWith('/dashboard');
// 已认证用户重定向离开认证页面
if (isAuthenticated && isAuthPage) {
return Response.redirect(new URL('/dashboard', req.nextUrl));
}
// 未认证用户从受保护路由重定向
if (!isAuthenticated && isProtectedRoute) {
return Response.redirect(new URL('/auth/signin', req.nextUrl));
}
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Session Strategies
会话策略
JWT Strategy (Default without adapter)
JWT策略(无适配器时的默认选项)
Best for: Serverless, Edge runtime, minimal database queries
typescript
export const { auth } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
});Characteristics:
- Sessions stored in encrypted cookies
- No database query per request
- Cannot be invalidated before expiration
- Works with Edge middleware
最适合:无服务器架构、Edge运行时、最少数据库查询
typescript
export const { auth } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30天
},
});特点:
- 会话存储在加密Cookie中
- 每次请求无需查询数据库
- 到期前无法失效
- 兼容Edge中间件
Database Strategy
数据库策略
Best for: Immediate session invalidation, "sign out everywhere"
typescript
export const { auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
});Characteristics:
- Sessions stored in database
- Database query on every request
- Can be invalidated immediately
- Incompatible with Edge middleware (use split config)
最适合:立即失效会话、“全设备登出”
typescript
export const { auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database',
maxAge: 30 * 24 * 60 * 60, // 30天
},
});特点:
- 会话存储在数据库中
- 每次请求都要查询数据库
- 可立即失效
- 与Edge中间件不兼容(使用拆分配置)
Split Configuration for Edge + Database
Edge + 数据库的拆分配置
typescript
// auth.config.ts - Edge-compatible config
import type { NextAuthConfig } from 'next-auth';
export const authConfig: NextAuthConfig = {
pages: {
signIn: '/auth/signin',
},
callbacks: {
authorized({ auth, request }) {
return !!auth?.user;
},
},
providers: [], // Configured in auth.ts
};
// auth.ts - Full config with adapter
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { authConfig } from './auth.config';
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
providers: [GitHub, Google],
});
// middleware.ts - Uses edge-compatible config
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;typescript
// auth.config.ts - 兼容Edge的配置
import type { NextAuthConfig } from 'next-auth';
export const authConfig: NextAuthConfig = {
pages: {
signIn: '/auth/signin',
},
callbacks: {
authorized({ auth, request }) {
return !!auth?.user;
},
},
providers: [], // 在auth.ts中配置
};
// auth.ts - 带适配器的完整配置
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { authConfig } from './auth.config';
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
providers: [GitHub, Google],
});
// middleware.ts - 使用兼容Edge的配置
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;Authentication in Components
组件中的身份验证
Server Components
服务器组件
typescript
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect('/auth/signin');
}
return (
<div>
<h1>Welcome, {session.user.name}!</h1>
<p>Email: {session.user.email}</p>
</div>
);
}typescript
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) {
redirect('/auth/signin');
}
return (
<div>
<h1>欢迎,{session.user.name}!</h1>
<p>邮箱:{session.user.email}</p>
</div>
);
}Client Components
客户端组件
typescript
'use client';
import { useSession } from 'next-auth/react';
export function UserProfile() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <Skeleton />;
}
if (status === 'unauthenticated') {
return <SignInPrompt />;
}
return (
<div>
<img src={session.user.image} alt={session.user.name} />
<p>{session.user.name}</p>
</div>
);
}typescript
'use client';
import { useSession } from 'next-auth/react';
export function UserProfile() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <Skeleton />;
}
if (status === 'unauthenticated') {
return <SignInPrompt />;
}
return (
<div>
<img src={session.user.image} alt={session.user.name} />
<p>{session.user.name}</p>
</div>
);
}Session Provider Setup
会话提供者设置
typescript
// 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 lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}typescript
// 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 lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Server Actions
服务器操作
typescript
'use server';
import { auth, signIn, signOut } from '@/auth';
// Sign in action
export async function handleSignIn(provider: string) {
await signIn(provider, { redirectTo: '/dashboard' });
}
// Sign out action
export async function handleSignOut() {
await signOut({ redirectTo: '/' });
}
// Protected action
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
const title = formData.get('title') as string;
await prisma.post.create({
data: {
title,
authorId: session.user.id,
},
});
revalidatePath('/posts');
}typescript
'use server';
import { auth, signIn, signOut } from '@/auth';
// 登录操作
export async function handleSignIn(provider: string) {
await signIn(provider, { redirectTo: '/dashboard' });
}
// 登出操作
export async function handleSignOut() {
await signOut({ redirectTo: '/' });
}
// 受保护的操作
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error('未授权');
}
const title = formData.get('title') as string;
await prisma.post.create({
data: {
title,
authorId: session.user.id,
},
});
revalidatePath('/posts');
}API Route Protection
API路由保护
typescript
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = await fetchUserData(session.user.id);
return NextResponse.json(data);
}typescript
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: '未授权' }, { status: 401 });
}
const data = await fetchUserData(session.user.id);
return NextResponse.json(data);
}Role-Based Access Control
基于角色的访问控制
Type Extensions
类型扩展
typescript
// 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;
}
}typescript
// 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;
}
}Role Check Utility
角色检查工具
typescript
import { auth } from '@/auth';
export async function requireRole(allowedRoles: string[]) {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
if (!allowedRoles.includes(session.user.role)) {
throw new Error('Forbidden');
}
return session;
}
// Usage
export default async function AdminPage() {
const session = await requireRole(['admin']);
return <AdminDashboard user={session.user} />;
}typescript
import { auth } from '@/auth';
export async function requireRole(allowedRoles: string[]) {
const session = await auth();
if (!session?.user) {
throw new Error('未授权');
}
if (!allowedRoles.includes(session.user.role)) {
throw new Error('禁止访问');
}
return session;
}
// 使用示例
export default async function AdminPage() {
const session = await requireRole(['admin']);
return <AdminDashboard user={session.user} />;
}OAuth Providers Configuration
OAuth服务商配置
GitHub
GitHub
typescript
import GitHub from 'next-auth/providers/github';
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
authorization: {
params: {
scope: 'read:user user:email',
},
},
});typescript
import GitHub from 'next-auth/providers/github';
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
authorization: {
params: {
scope: 'read:user user:email',
},
},
});typescript
import Google from 'next-auth/providers/google';
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
});typescript
import Google from 'next-auth/providers/google';
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
});Credentials Provider
凭证服务商
typescript
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
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 prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user || !user.hashedPassword) {
return null;
}
const isValid = await bcrypt.compare(credentials.password, user.hashedPassword);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
},
});typescript
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
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 prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user || !user.hashedPassword) {
return null;
}
const isValid = await bcrypt.compare(credentials.password, user.hashedPassword);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
},
});Security Best Practices
安全最佳实践
1. Always Use AUTH_SECRET in Production
1. 生产环境中务必使用AUTH_SECRET
bash
undefinedbash
undefinedGenerate a secure secret
生成安全密钥
openssl rand -base64 32
undefinedopenssl rand -base64 32
undefined2. Cookie Configuration
2. Cookie配置
typescript
export const { auth } = NextAuth({
cookies: {
sessionToken: {
name: `__Secure-authjs.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
});typescript
export const { auth } = NextAuth({
cookies: {
sessionToken: {
name: `__Secure-authjs.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
});3. CSRF Protection
3. CSRF保护
Auth.js handles CSRF protection automatically. Ensure you:
- Use POST for sign-in/sign-out
- Don't disable built-in protections
Auth.js会自动处理CSRF保护。请确保:
- 登录/登出使用POST请求
- 不要禁用内置保护机制
4. Validate Sessions Server-Side
4. 服务器端验证会话
typescript
// Always verify on the server for sensitive operations
export async function sensitiveOperation() {
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
// Double-check user exists in database
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
if (!user || user.banned) {
throw new Error('Access denied');
}
// Proceed with operation
}typescript
// 敏感操作务必在服务器端验证
export async function sensitiveOperation() {
const session = await auth();
if (!session?.user) {
throw new Error('未授权');
}
// 双重检查用户是否存在于数据库
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
if (!user || user.banned) {
throw new Error('访问被拒绝');
}
// 执行操作
}Session Refresh and Polling
会话刷新与轮询
typescript
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider
refetchInterval={5 * 60} // Refetch every 5 minutes
refetchOnWindowFocus={true}
>
{children}
</SessionProvider>
);
}typescript
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider
refetchInterval={5 * 60} // 每5分钟刷新一次
refetchOnWindowFocus={true}
>
{children}
</SessionProvider>
);
}Error Handling
错误处理
Custom Error Page
自定义错误页面
typescript
// app/auth/error/page.tsx
export default function AuthErrorPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
const errorMessages: Record<string, string> = {
Configuration: 'There is a problem with the server configuration.',
AccessDenied: 'You do not have permission to sign in.',
Verification: 'The verification link has expired or has already been used.',
Default: 'An error occurred during authentication.',
};
const error = searchParams.error || 'Default';
const message = errorMessages[error] || errorMessages.Default;
return (
<div>
<h1>Authentication Error</h1>
<p>{message}</p>
<a href="/auth/signin">Try again</a>
</div>
);
}typescript
// app/auth/error/page.tsx
export default function AuthErrorPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
const errorMessages: Record<string, string> = {
Configuration: '服务器配置存在问题。',
AccessDenied: '您没有登录权限。',
Verification: '验证链接已过期或已被使用。',
Default: '身份验证过程中发生错误。',
};
const error = searchParams.error || 'Default';
const message = errorMessages[error] || errorMessages.Default;
return (
<div>
<h1>身份验证错误</h1>
<p>{message}</p>
<a href="/auth/signin">重试</a>
</div>
);
}Testing
测试
typescript
// Mock auth for testing
import { auth } from '@/auth';
jest.mock('@/auth', () => ({
auth: jest.fn(),
}));
describe('Protected API', () => {
it('returns 401 for unauthenticated requests', async () => {
(auth as jest.Mock).mockResolvedValue(null);
const response = await GET();
expect(response.status).toBe(401);
});
it('returns data for authenticated requests', async () => {
(auth as jest.Mock).mockResolvedValue({
user: { id: '1', email: 'test@example.com' },
});
const response = await GET();
expect(response.status).toBe(200);
});
});typescript
// 测试时模拟auth
import { auth } from '@/auth';
jest.mock('@/auth', () => ({
auth: jest.fn(),
}));
describe('受保护的API', () => {
it('对未认证请求返回401', async () => {
(auth as jest.Mock).mockResolvedValue(null);
const response = await GET();
expect(response.status).toBe(401);
});
it('对已认证请求返回数据', async () => {
(auth as jest.Mock).mockResolvedValue({
user: { id: '1', email: 'test@example.com' },
});
const response = await GET();
expect(response.status).toBe(200);
});
});Common Issues and Solutions
常见问题与解决方案
Session Disappears on Refresh
刷新后会话消失
- Ensure is set in production
AUTH_SECRET - Check cookie configuration
- Verify HTTPS in production
- 确保生产环境中设置了
AUTH_SECRET - 检查Cookie配置
- 验证生产环境使用HTTPS
Edge Runtime Compatibility
Edge运行时兼容性
Use split configuration if using database adapter with Edge middleware.
如果在Edge中间件中使用数据库适配器,请使用拆分配置。
Type Errors with Custom Properties
自定义属性的类型错误
Extend the types in .
types/next-auth.d.ts在中扩展类型。
types/next-auth.d.tsCommon Anti-Patterns to Avoid
需避免的常见反模式
- Using prefix (use
NEXTAUTH_in v5)AUTH_ - Not setting in production
AUTH_SECRET - Relying on client-side session checks for authorization
- Not handling loading states in client components
- Using database strategy with Edge middleware
- Not validating sessions in server actions
- Exposing sensitive data in JWT tokens
- 使用前缀(v5中请使用
NEXTAUTH_)AUTH_ - 生产环境中未设置
AUTH_SECRET - 依赖客户端会话检查进行授权
- 客户端组件中未处理加载状态
- 在Edge中间件中使用数据库策略
- 服务器操作中未验证会话
- 在JWT令牌中暴露敏感数据