0Pricing
React Academy · 课时

凭据提供商与自定义登录

使用凭据提供商和自定义登录表单,根据数据库验证用户身份。

凭据提供商与自定义登录 是 CoddyKit 上的免费 React Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。

何时使用凭据提供商

当您要针对自己的数据库进行身份验证(电子邮件加密码),而不是使用 OAuth 提供商时,请使用凭据提供商。您必须谨慎处理密码哈希和安全问题。

定义凭据提供商

将 Credentials 添加到提供商数组中。authorize 函数会接收提交的凭据,并返回用户对象或 null。

import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';

export const { handlers, auth } = NextAuth({
  providers: [
    Credentials({
      name: 'Credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        const user = await db.user.findUnique({ where: { email: credentials.email as string } });
        if (!user) return null;
        const valid = await bcrypt.compare(credentials.password as string, user.passwordHash);
        if (!valid) return null;
        return { id: user.id, name: user.name, email: user.email };
      },
    }),
  ],
});

自定义登录表单

将登录表单构建为客户端组件,并从 next-auth/react 调用 signIn('credentials', { email, password })。

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

export function LoginForm() {
  const [error, setError] = useState('');

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const form = new FormData(e.currentTarget);
    const result = await signIn('credentials', {
      email: form.get('email'),
      password: form.get('password'),
      redirect: false, // handle redirect manually
    });
    if (result?.error) setError('Invalid email or password');
    else router.push('/dashboard');
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="error">{error}</p>}
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit">Sign in</button>
    </form>
  );
}

使用 redirect: false 处理错误

向 signIn() 传入 redirect: false,即可接收结果对象,并在组件中处理错误,而不是被重定向到错误页面。

const result = await signIn('credentials', { email, password, redirect: false });
if (result?.error) {
  // result.error is 'CredentialsSignin' for auth failures
  setError('Invalid email or password');
}

在 authorize 中使用 Zod 验证

在访问数据库之前,在 authorize 函数中使用 Zod 验证凭据。

import { z } from 'zod';

const schema = z.object({ email: z.string().email(), password: z.string().min(8) });

async authorize(credentials) {
  const parsed = schema.safeParse(credentials);
  if (!parsed.success) return null;
  const { email, password } = parsed.data;
  // ... db lookup
}

凭据的会话策略

凭据提供程序要求使用 JWT 会话策略(会话无需数据库适配器)。请明确设置 session: { strategy: 'jwt' }。

export const { handlers, auth } = NextAuth({
  session: { strategy: 'jwt' },
  providers: [Credentials({ ... })],
});

向会话添加自定义字段

通过 JWT 和会话回调传递额外的用户数据,以便在 useSession() 中获取这些数据。

callbacks: {
  async jwt({ token, user }) {
    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;
  },
},

密码哈希

存储密码前,请始终使用 bcrypt 对其进行哈希处理。绝不要存储明文密码。

import bcrypt from 'bcryptjs';

// On registration:
const passwordHash = await bcrypt.hash(password, 12);
await db.user.create({ data: { email, passwordHash } });

// On login (in authorize):
const valid = await bcrypt.compare(submittedPassword, user.passwordHash);

限制登录尝试次数

请使用中间件或内存存储限制登录尝试次数,以保护凭据端点免受暴力破解。

// middleware.ts or in a rate-limiting library
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10 s') });

// Check in authorize before DB query:
const { success } = await ratelimit.limit(credentials.email as string);
if (!success) return null;

注册流程

注册独立于 Auth.js — 请通过服务器操作或 API 路由将用户创建到数据库中(密码需经过哈希处理),然后使用 signIn('credentials', ...) 为其登录。

快速检查

在自定义登录表单中调用 signIn() 时,为什么应传入 redirect: false?

回顾

凭据提供程序会将提交的表单数据传递给您的 authorize 函数。请使用 Zod 验证数据,使用 bcrypt 比较哈希后的密码,并返回用户对象或 null。在表单中使用 redirect: false 以便自定义处理错误,并始终将凭据与 JWT 策略结合使用。

常见问题解答

「凭据提供商与自定义登录」课时是免费的吗?

是的 — 「凭据提供商与自定义登录」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。

「凭据提供商与自定义登录」这节课中我会学到什么?

使用凭据提供商和自定义登录表单,根据数据库验证用户身份。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「凭据提供商与自定义登录」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Academy 课中编写并运行代码吗?

能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 在 Next.js App Router 中设置 Auth.js
  2. 会话管理与 useSession 钩子
  3. 凭据提供商与自定义登录
  4. 基于中间件的路由保护
← 返回 React Academy