凭据
要将 Auth.js 与任何外部身份验证机制设置起来,或者使用传统的用户名/电子邮件和密码流,我们可以使用 Credentials
提供商。此提供商旨在将插入登录表单中的任何凭据(例如用户名/密码,但不限于此)转发到您的身份验证服务。
⚠️
自从用户名和密码作为向 Web 应用程序验证和授权用户的首选机制以来,该行业已经取得了长足的进步。因此,如果可能,我们建议使用更现代、更安全的身份验证机制,例如任何 OAuth 提供商、电子邮件魔法链接 或 WebAuthn(密钥) 选项。
但是,我们也希望保持灵活性,并支持您认为适合您的应用程序和用例的任何内容,因此我们没有计划删除此提供商。
💡
默认情况下,Credentials 提供商不会在数据库中持久保存数据。但是,您仍然可以在数据库中创建和保存任何数据,您只需要提供必要的逻辑,例如加密密码、添加速率限制、添加密码重置功能等。
Credentials 提供商
首先,让我们在 Auth.js 配置文件中初始化 Credentials
提供商。您需要导入提供商并将其添加到您的 providers
数组中。
./auth.ts
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
let user = null
// logic to salt and hash password
const pwHash = saltAndHashPassword(credentials.password)
// logic to verify if the user exists
user = await getUserFromDb(credentials.email, pwHash)
if (!user) {
// No user found, so this is their first attempt to login
// Optionally, this is also the place you could do a user registration
throw new Error("Invalid credentials.")
}
// return user object with their profile data
return user
},
}),
],
})
如果您使用的是 TypeScript,您可以 扩展 User
接口 以匹配您的 authorize
回调的响应,因此每当您在其他回调(例如 jwt
)中读取用户时,类型都会正确匹配。
登录表单
最后,让我们创建一个简单的登录表单。
./components/sign-in.tsx
import { signIn } from "@/auth"
export function SignIn() {
return (
<form
action={async (formData) => {
"use server"
await signIn("credentials", formData)
}}
>
<label>
Email
<input name="email" type="email" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button>Sign In</button>
</form>
)
}
验证凭据
始终在服务器端验证凭据,例如利用像 Zod 这样的模式验证库。
npm install zod
接下来,我们将在我们的 auth.ts
配置文件中设置模式和解析,使用 Credentials
提供商上的 authorize
回调。
./lib/zod.ts
import { object, string } from "zod"
export const signInSchema = object({
email: string({ required_error: "Email is required" })
.min(1, "Email is required")
.email("Invalid email"),
password: string({ required_error: "Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters"),
})
./auth.ts
import NextAuth from "next-auth"
import { ZodError } from "zod"
import Credentials from "next-auth/providers/credentials"
import { signInSchema } from "./lib/zod"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
import { getUserFromDb } from "@/utils/db"
export const { handlers, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
try {
let user = null
const { email, password } = await signInSchema.parseAsync(credentials)
// logic to salt and hash password
const pwHash = saltAndHashPassword(password)
// logic to verify if the user exists
user = await getUserFromDb(email, pwHash)
if (!user) {
throw new Error("Invalid credentials.")
}
// return JSON object with the user data
return user
} catch (error) {
if (error instanceof ZodError) {
// Return `null` to indicate that the credentials are invalid
return null
}
}
},
}),
],
})