mcpField
N
nextauth

nextauth

github.com/claude-dev-suite安全4640 次安装2 天前更新
代码仓库

关于 nextauth

面向 Next.js 应用开发者的 NextAuth.js 身份验证技能,介绍 App Router 配置、客户端与服务端会话处理及生产安全实践。内容还包括常见问题排查与测试示例。

主要功能

  1. 01App Router 身份验证配置
  2. 02客户端与服务端会话处理
  3. 03Provider 与 Adapter 示例
  4. 04生产安全配置建议
  5. 05故障排查和测试示例

适用场景

  1. 01为 Next.js 应用添加登录
  2. 02在服务端保护页面
  3. 03检查身份验证配置与会话问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill nextauth

安装后在 Claude Code 中输入/nextauth确认已加载。

附带文件quick-ref/callbacks.mdquick-ref/setup.md

详细介绍

NextAuth.js 核心知识

深入了解:使用 mcp__documentation__fetch_docs,并将 technology 设为 nextauth,以获取全面的文档。

基本设置(App Router)

// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
// lib/auth.ts
import { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import CredentialsProvider from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './prisma';

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        const user = await prisma.user.findUnique({
          where: { email: credentials?.email },
        });
        if (user && await verifyPassword(credentials?.password, user.password)) {
          return user;
        }
        return null;
      },
    }),
  ],
  callbacks: {
    async session({ session, user }) {
      session.user.id = user.id;
      return session;
    },
  },
  pages: {
    signIn: '/login',
    error: '/auth/error',
  },
};

客户端用法

'use client';
import { useSession, signIn, signOut } from 'next-auth/react';

function AuthButton() {
  const { data: session, status } = useSession();

  if (status === 'loading') return <Spinner />;

  if (session) {
    return (
      <div>
        <span>{session.user?.email}</span>
        <button onClick={() => signOut()}>Sign out</button>
      </div>
    );
  }

  return <button onClick={() => signIn('google')}>Sign in</button>;
}

服务端身份验证

// In Server Component
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';

async function ProtectedPage() {
  const session = await getServerSession(authOptions);

  if (!session) {
    redirect('/login');
  }

  return <div>Welcome {session.user.name}</div>;
}

不应使用此技能的情况

  • 通用 OAuth 2.0 流程 —— 使用 oauth2 技能处理与平台无关的 OAuth
  • 自定义 JWT 实现 —— 使用 jwt 技能处理自定义令牌逻辑
  • 非 Next.js 框架 —— 使用框架专用的身份验证方案(Express Passport 等)
  • Remix/SvelteKit —— 使用它们原生的身份验证方案

类型扩展

// types/next-auth.d.ts
declare module 'next-auth' {
  interface Session {
    user: { id: string; role: string } & DefaultSession['user'];
  }
}

反模式

反模式问题所在正确做法
未设置 NEXTAUTH_SECRET安全漏洞在生产环境中始终设置
仅在客户端检查会话可被绕过使用 getServerSession()
提供商凭据硬编码存在安全风险使用环境变量
未处理错误用户体验差实现自定义错误页面
混用会话策略行为不一致统一使用 JWT 或数据库
未启用 CSRF 防护容易受到攻击使用默认的 CSRF 防护(默认已启用)

快速故障排除

问题原因解决方案
"Configuration error"缺少必需的环境变量检查 NEXTAUTH_URL 和 NEXTAUTH_SECRET
会话为 null未通过身份验证或会话已过期检查是否调用了 signIn()
"Callback URL error"重定向无效在提供商设置中将 URL 加入白名单
类型错误缺少类型扩展创建 types/next-auth.d.ts
会话未更新缓存问题调用 useSession 中的 update()
CORS 错误域名错误确保 NEXTAUTH_URL 与部署 URL 一致

生产环境就绪情况

安全配置
// lib/auth.ts
import { NextAuthOptions } from 'next-auth';

export const authOptions: NextAuthOptions = {
  // Secure session configuration
  session: {
    strategy: 'jwt',
    maxAge: 30 * 24 * 60 * 60, // 30 days
  },

  // Secure cookies
  cookies: {
    sessionToken: {
      name: process.env.NODE_ENV === 'production'
        ? '__Secure-next-auth.session-token'
        : 'next-auth.session-token',
      options: {
        httpOnly: true,
        sameSite: 'lax',
        path: '/',
        secure: process.env.NODE_ENV === 'production',
      },
    },
  },

  // Callbacks for security
  callbacks: {
    async jwt({ token, user, account }) {
      if (user) {
        token.id = user.id;
        token.role = user.role;
      }
      return token;
    },
    async session({ session, token }) {
      session.user.id = token.id as string;
      session.user.role = token.role as string;
      return session;
    },
    async signIn({ user, account, profile }) {
      // Block suspicious sign-ins
      const isAllowed = await checkUserAllowed(user.email);
      return isAllowed;
    },
  },

  // Security events
  events: {
    async signIn({ user, account }) {
      await logSecurityEvent('signin', { userId: user.id, provider: account?.provider });
    },
    async signOut({ token }) {
      await logSecurityEvent('signout', { userId: token.sub });
    },
  },
};
速率限制
// middleware.ts
import { NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '1 m'), // 5 requests per minute
});

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/auth')) {
    const ip = request.ip ?? '127.0.0.1';
    const { success, limit, reset, remaining } = await ratelimit.limit(ip);

    if (!success) {
      return new NextResponse('Too Many Requests', {
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': reset.toString(),
        },
      });
    }
  }

  return NextResponse.next();
}
CSRF 防护
// lib/auth.ts
export const authOptions: NextAuthOptions = {
  // Enable CSRF token verification
  useSecureCookies: process.env.NODE_ENV === 'production',

  // Custom CSRF token
  callbacks: {
    async redirect({ url, baseUrl }) {
      // Only allow redirects to same origin
      if (url.startsWith('/')) return `${baseUrl}${url}`;
      if (new URL(url).origin === baseUrl) return url;
      return baseUrl;
    },
  },
};

// In API routes, verify CSRF
import { getToken } from 'next-auth/jwt';

export async function POST(request: Request) {
  const token = await getToken({ req: request });
  if (!token) {
    return new Response('Unauthorized', { status: 401 });
  }
  // Process request
}
错误处理
// app/auth/error/page.tsx
'use client';

import { useSearchParams } from 'next/navigation';

const errorMessages: Record<string, string> = {
  Configuration: 'Server configuration error',
  AccessDenied: 'Access denied',
  Verification: 'Verification link expired',
  Default: 'Authentication error',
};

export default function AuthError() {
  const searchParams = useSearchParams();
  const error = searchParams.get('error') ?? 'Default';

  return (
    <div className="error-page">
      <h1>Authentication Error</h1>
      <p>{errorMessages[error] ?? errorMessages.Default}</p>
      <a href="/login">Try again</a>
    </div>
  );
}
测试
// __tests__/auth.test.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';

// Mock next-auth
jest.mock('next-auth', () => ({
  getServerSession: jest.fn(),
}));

describe('Protected Route', () => {
  it('redirects unauthenticated users', async () => {
    (getServerSession as jest.Mock).mockResolvedValue(null);

    const response = await fetch('/api/protected');
    expect(response.status).toBe(401);
  });

  it('allows authenticated users', async () => {
    (getServerSession as jest.Mock).mockResolvedValue({
      user: { id: '1', email: '[email protected]', role: 'user' },
    });

    const response = await fetch('/api/protected');
    expect(response.status).toBe(200);
  });
});

// E2E with Playwright
test('OAuth flow', async ({ page }) => {
  await page.goto('/login');
  await page.click('button:has-text("Sign in with Google")');

  // Mock OAuth provider response in test environment
  await expect(page).toHaveURL('/dashboard');
});
监控指标
指标目标
登录成功率> 99%
身份验证延迟< 200ms
登录失败次数监控并告警
令牌刷新成功率> 99.9%
检查清单
  • 安全的 Cookie 配置
  • 为 JWT 设置合适的 maxAge
  • 对身份验证端点实施速率限制
  • 启用 CSRF 防护
  • 验证重定向 URL
  • 记录安全事件
  • 自定义错误页面
  • 会话刷新策略
  • 基于角色的访问控制
  • 使用模拟会话进行测试

参考文档

评论

支持 Markdown