nextauth-authentication

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

NextAuth 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
    auth()
    function
  • Implement proper session management strategy based on your needs
  • Always validate sessions server-side for sensitive operations
  • Configure environment variables correctly with the
    AUTH_
    prefix
  • 使用Auth.js v5模式和通用的
    auth()
    函数
  • 根据需求实施合适的会话管理策略
  • 对于敏感操作,始终在服务器端验证会话
  • 使用
    AUTH_
    前缀正确配置环境变量

Installation

安装

bash
npm install next-auth@beta
bash
npm install next-auth@beta

Environment Variables

环境变量

bash
undefined
bash
undefined

Required

必填项

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(多数环境下会自动检测)

undefined
undefined

Basic 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',
    },
  },
});

Google

Google

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
undefined
bash
undefined

Generate a secure secret

生成安全密钥

openssl rand -base64 32
undefined
openssl rand -base64 32
undefined

2. 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

刷新后会话消失

  1. Ensure
    AUTH_SECRET
    is set in production
  2. Check cookie configuration
  3. Verify HTTPS in production
  1. 确保生产环境中设置了
    AUTH_SECRET
  2. 检查Cookie配置
  3. 验证生产环境使用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.ts
中扩展类型。

Common Anti-Patterns to Avoid

需避免的常见反模式

  1. Using
    NEXTAUTH_
    prefix (use
    AUTH_
    in v5)
  2. Not setting
    AUTH_SECRET
    in production
  3. Relying on client-side session checks for authorization
  4. Not handling loading states in client components
  5. Using database strategy with Edge middleware
  6. Not validating sessions in server actions
  7. Exposing sensitive data in JWT tokens
  1. 使用
    NEXTAUTH_
    前缀(v5中请使用
    AUTH_
  2. 生产环境中未设置
    AUTH_SECRET
  3. 依赖客户端会话检查进行授权
  4. 客户端组件中未处理加载状态
  5. 在Edge中间件中使用数据库策略
  6. 服务器操作中未验证会话
  7. 在JWT令牌中暴露敏感数据