📚 现代前端开发 目录
阶段5:全栈与部署
第 32 / 35 课

身份认证:NextAuth

安全可靠的用户认证

📖 核心概念

💻 代码实现

NextAuth配置 ✅ typescript
// auth.ts
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_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: '邮箱', type: 'email' },
        password: { label: '密码', type: 'password' },
      },
      async authorize(credentials) {
        const validated = loginSchema.safeParse(credentials);
        if (!validated.success) return null;

        const user = await prisma.user.findUnique({
          where: { email: validated.data.email },
        });

        if (!user?.password) return null;
        const isValid = await bcrypt.compare(validated.data.password, user.password);
        if (!isValid) return null;

        return { id: user.id, email: user.email, name: user.name };
      },
    }),
  ],
  session: { strategy: 'jwt', maxAge: 7 * 24 * 60 * 60 },
  pages: {
    signIn: '/login',
    error: '/auth/error',
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.role = (user as any).role;
      }
      return token;
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string;
        (session.user as any).role = token.role;
      }
      return session;
    },
  },
});

// app/api/auth/[...nextauth]/route.ts
export const { GET, POST } = handlers;

// 中间件保护路由
// middleware.ts
export { auth as middleware } from '@/auth';

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};
认证组件与API保护 ✅ tsx
// 组件中获取用户信息
import { auth } from '@/auth';

// Server Component
export default async function DashboardPage() {
  const session = await auth();

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

  return (
    <div>
      <h1>欢迎, {session.user.name}</h1>
      <img src={session.user.image} alt="avatar" />
    </div>
  );
}

// Client Component
'use client';
import { useSession } from 'next-auth/react';

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

  if (status === 'loading') return <Skeleton />;
  if (status === 'unauthenticated') return <LoginButton />;

  return (
    <div className="user-menu">
      <img src={session.user.image} alt="" />
      <span>{session.user.name}</span>
      <button onClick={() => signOut()}>退出</button>
    </div>
  );
}

// 保护API路由
import { auth } from '@/auth';

export async function POST(request: Request) {
  const session = await auth();
  if (!session) {
    return Response.json({ error: '未授权' }, { status: 401 });
  }

  // 只允许管理员
  if ((session.user as any).role !== 'ADMIN') {
    return Response.json({ error: '权限不足' }, { status: 403 });
  }

  const body = await request.json();
  // 处理请求...
}

// 注册页面
'use client';
import { useState } from 'react';
import { signIn } from 'next-auth/react';

function RegisterForm() {
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setLoading(true);

    const formData = new FormData(e.currentTarget);

    // 1. 创建账号
    const res = await fetch('/api/auth/register', {
      method: 'POST',
      body: JSON.stringify({
        name: formData.get('name'),
        email: formData.get('email'),
        password: formData.get('password'),
      }),
    });

    if (res.ok) {
      // 2. 自动登录
      await signIn('credentials', {
        email: formData.get('email'),
        password: formData.get('password'),
        callbackUrl: '/dashboard',
      });
    }
    setLoading(false);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="姓名" required />
      <input name="email" type="email" placeholder="邮箱" required />
      <input name="password" type="password" placeholder="密码" required />
      <button type="submit" disabled={loading}>注册</button>

      <div className="divider">或</div>

      <button type="button" onClick={() => signIn('github')}>
        GitHub 登录
      </button>
      <button type="button" onClick={() => signIn('google')}>
        Google 登录
      </button>
    </form>
  );
}

🎯 练习任务

🏆 成就解锁
认证安全专家 — 掌握NextAuth身份认证,构建安全的用户系统