You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next-Auth+Prisma下Google/Facebook授权用户角色丢失问题求助

解决Next-Auth第三方登录时角色无法存储的问题

核心问题分析

第三方OAuth登录(Google/Facebook)时,Provider不会返回自定义的角色信息,且用户没有机会在登录流程中选择角色,直接修改profile配置无法解决——因为数据源本身就没有角色字段。以下是两种可行的解决方案:


方案一:首次登录后强制引导用户选择角色

这种方式更符合业务逻辑,让用户自主选择角色,步骤如下:

1. 确保Prisma User模型包含角色字段

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  role          Role?     // 设为可选,首次创建时为空
  accounts      Account[]
  sessions      Session[]
}

enum Role {
  Events
  Brand
  Athlete
}

2. 配置Next-Auth回调,传递角色状态

在auth.ts中修改AuthOptions:

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/lib/prisma";

export const authOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    FacebookProvider({
      clientId: process.env.FACEBOOK_CLIENT_ID!,
      clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    async session({ session, user }) {
      // 将角色挂载到session,供前端判断
      session.user.role = user.role;
      return session;
    },
    async jwt({ token, user }) {
      // 将角色存入JWT,供中间件使用
      if (user) token.role = user.role;
      return token;
    },
  },
};

export default NextAuth(authOptions);

3. 用Middleware拦截未设置角色的用户

创建middleware.ts,强制未选角色的用户进入角色选择页:

import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";

export default withAuth(
  function middleware(req) {
    const { pathname } = req.nextUrl;
    const role = req.nextauth.token?.role;

    // 已登录但无角色,且不在选择页时跳转
    if (!role && pathname !== "/select-role") {
      return NextResponse.redirect(new URL("/select-role", req.url));
    }
    // 已选角色,访问选择页时跳首页
    if (role && pathname === "/select-role") {
      return NextResponse.redirect(new URL("/", req.url));
    }
  },
  {
    callbacks: {
      authorized: ({ token }) => !!token, // 仅对已登录用户生效
    },
  }
);

export const config = {
  matcher: ["/((?!api|auth|_next/static|_next/image|favicon.ico).*)"],
};

4. 实现角色选择页面与更新接口

创建app/select-role/page.tsx:

"use client";

import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";

export default function SelectRolePage() {
  const { data: session } = useSession();
  const router = useRouter();
  const [selectedRole, setSelectedRole] = useState<"Events" | "Brand" | "Athlete">("Athlete");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!session?.user?.id) return;

    const res = await fetch("/api/update-role", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ role: selectedRole }),
    });

    if (res.ok) router.push("/");
  };

  return (
    <div className="max-w-md mx-auto mt-10">
      <h1 className="text-2xl font-bold mb-6">选择你的角色</h1>
      <form onSubmit={handleSubmit}>
        <div className="space-y-4">
          {["Events", "Brand", "Athlete"].map(role => (
            <label key={role} className="flex items-center gap-2">
              <input
                type="radio"
                name="role"
                value={role}
                checked={selectedRole === role}
                onChange={() => setSelectedRole(role as any)}
              />
              {role}
            </label>
          ))}
        </div>
        <button
          type="submit"
          className="mt-6 w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600"
        >
          确认选择
        </button>
      </form>
    </div>
  );
}

创建app/api/update-role/route.ts:

import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const session = await getServerSession(authOptions);
  if (!session?.user?.id) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { role } = await req.json();
  if (!["Events", "Brand", "Athlete"].includes(role)) {
    return NextResponse.json({ error: "Invalid role" }, { status: 400 });
  }

  await prisma.user.update({
    where: { id: session.user.id },
    data: { role },
  });

  return NextResponse.json({ success: true });
}

方案二:为第三方登录用户分配默认角色

如果不需要用户自主选择,可直接给第三方登录用户分配默认角色:

1. 用Prisma模型默认值

修改Prisma User模型,给角色设置默认值:

role          Role      @default(Athlete)

2. 按Provider自定义默认角色(可选)

如果需要根据登录渠道设置不同默认角色,在AuthOptions中添加createUser回调:

callbacks: {
  async createUser({ user, account }) {
    let defaultRole = "Athlete";
    if (account?.provider === "google") defaultRole = "Brand";
    if (account?.provider === "facebook") defaultRole = "Events";

    await prisma.user.update({
      where: { id: user.id },
      data: { role: defaultRole },
    });
    return user;
  },
},

内容的提问来源于stack exchange,提问作者Andre Teizen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:46:26