憑證
若要使用任何外部驗證機制設定 Auth.js,或使用傳統的使用者名稱/電子郵件和密碼流程,我們可以使用 Credentials
供應商。此供應商旨在將任何插入登入表單的憑證(例如使用者名稱/密碼,但不限於)轉發至您的驗證服務。
⚠️
自從使用者名稱和密碼成為驗證和授權使用者存取 Web 應用程式的主要機制以來,業界已經取得了長足的進步。因此,如果可能,我們建議使用更現代、更安全的驗證機制,例如任何 OAuth 供應商、電子郵件魔法連結,或 WebAuthn (通行金鑰) 選項。
但是,我們也希望具有彈性並支援您認為適合您的應用程式和使用案例的任何內容,因此目前沒有移除此供應商的計畫。
💡
預設情況下,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
接下來,我們將使用 Credentials
供應商上的 authorize
回呼,在我們的 auth.ts
設定檔中設定架構和剖析。
./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
}
}
},
}),
],
})