next-auth v5 结合 Prisma 实现登录与会话管理
1. 安装依赖
npm install next-auth prisma @prisma/client
2. 配置 Prisma 模型
在 prisma/schema.prisma 文件中定义 User 和 Account 模型(next-auth v5 推荐使用自定义模型)。
model User {
id String @id @default(uuid())
email String @unique
name String?
image String?
hashedPassword String?
accounts Account[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(uuid())
userId String
provider String
providerAccountId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
运行 prisma migrate
同步数据库:
npx prisma migrate dev --name init
3. 创建 next-auth 配置
在 pages/api/auth/[...nextauth].ts
文件中添加 next-auth 配置:
import NextAuth, { AuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaClient } from "@prisma/client";
import { compare } from "bcryptjs"; // 假设密码是加密存储的
const prisma = new PrismaClient();
export const authOptions: AuthOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials.password) {
throw new Error("Missing email or password");
}
// 查找用户
const user = await prisma.user.findUnique({
where: { email: credentials.email },
});
if (!user) {
throw new Error("No user found");
}
// 验证密码
const isValidPassword = await compare(
credentials.password,
user.hashedPassword
);
if (!isValidPassword) {
throw new Error("Invalid credentials");
}
return { id: user.id, email: user.email, name: user.name };
},
}),
],
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (token) {
session.user.id = token.id;
}
return session;
},
},
};
export default NextAuth(authOptions);`
4. 创建登录页面
示例的登录页面:
import { signIn } from "next-auth/react";
import { useState } from "react";
export default function LoginPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await signIn("credentials", {
email,
password,
redirect: false,
});
if (result?.error) {
setError(result.error);
} else {
// 登录成功,跳转
window.location.href = "/";
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">Login</button>
</form>
);
}
5. 使用会话信息
在需要使用用户会话的页面中:
import { useSession, signOut } from "next-auth/react";
export default function Dashboard() {
const { data: session, status } = useSession();
if (status === "loading") {
return <p>Loading...</p>;
}
if (!session) {
return <p>You are not logged in</p>;
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
6. 运行应用
npm run dev
关键点说明
密码存储:建议在用户注册时使用 bcrypt 对密码进行加密存储。
Session 策略:此示例使用 JWT 策略,如果需要使用数据库会话,可以将 session.strategy 设置为 “database” 并配置 Prisma 的 Session 模型。
错误处理:authorize 方法中抛出的错误会显示在客户端,可以根据需求自定义错误提示。