next-auth v5 结合 Prisma 实现登录与会话管理

1. 安装依赖

复制代码
npm install next-auth prisma @prisma/client

2. 配置 Prisma 模型

在 prisma/schema.prisma 文件中定义 User 和 Account 模型(next-auth v5 推荐使用自定义模型)。

javascript 复制代码
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 配置:

javascript 复制代码
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. 创建登录页面

示例的登录页面:

javascript 复制代码
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. 使用会话信息

在需要使用用户会话的页面中:

javascript 复制代码
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 方法中抛出的错误会显示在客户端,可以根据需求自定义错误提示。

相关推荐
2501_944448003 分钟前
Flutter for OpenHarmony衣橱管家App实战:预算管理实现
前端·javascript·flutter
2501_944448007 分钟前
Flutter for OpenHarmony衣橱管家App实战:意见反馈功能实现
android·javascript·flutter
笨蛋不要掉眼泪9 分钟前
Redis持久化解析:RDB和AOF的对比
前端·javascript·redis
心.c11 分钟前
Vue3+Node.js实现文件上传分片上传和断点续传【详细教程】
前端·javascript·vue.js·算法·node.js·哈希算法
妙团团13 分钟前
React学习之自定义tab组合组件
javascript·学习·react.js
2601_9498095919 分钟前
flutter_for_openharmony家庭相册app实战+隐私设置实现
android·javascript·flutter
2601_9495936520 分钟前
React Native 鸿蒙跨平台开发:LinearGradient 渐变动画效果
javascript·react native·react.js
qq_1777673722 分钟前
React Native鸿蒙跨平台音乐播放器涉及实时进度更新、播放控制、列表交互、状态管理等核心技术点
javascript·react native·react.js·ecmascript·交互·harmonyos
2501_9209317026 分钟前
React Native鸿蒙跨平台实现了简单的商品图片轮播功能,为用户提供了直观的商品图片浏览体验,帮助用户全面了解商品外观
javascript·react native·react.js·ecmascript·harmonyos
切糕师学AI38 分钟前
Vue 中如何修改地址栏参数并重新加载?
前端·javascript·vue.js