如何在Next-Auth中分离登录流程与权限授权流程?
实现Google登录与Google Ads权限授权分离
问题核心
使用NextAuth实现Google登录时,需将基础登录(仅获取用户信息)与Google Ads权限授权流程分离:用户先完成基础登录,后续可自主选择是否关联Google Ads账户,此时再触发Google的权限授权提示;但当前遇到授权后数据库中存储的账户scope未更新,导致调用Google Ads API时出现PERMISSION_DENIED: Request had insufficient authentication scopes.错误。
解决方案
1. 动态调整Google Provider的授权Scope
在NextAuth的API路由中,根据前端传递的自定义参数动态设置授权Scope,区分基础登录和Ads权限授权请求:
// ./src/pages/api/[...nextauth].ts import NextAuth from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { prisma } from "../../server/db/client"; import { env } from "../../env/server.mjs"; export default NextAuth({ adapter: PrismaAdapter(prisma), providers: [ GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, // 动态生成授权配置 authorization: (params) => { // 从请求Query中获取自定义标识 const askForAdsPerm = params?.req?.query?.askForGoogleAdsPermissions === "true"; // 基础登录Scope const baseScopes = "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile"; // 拼接完整Scope const finalScopes = askForAdsPerm ? `${baseScopes} https://www.googleapis.com/auth/adwords` : baseScopes; return { url: "https://accounts.google.com/o/oauth2/v2/auth", params: { scope: finalScopes, response_type: "code", // 确保请求额外权限时强制弹出授权提示 prompt: askForAdsPerm ? "consent" : "select_account", }, }; }, }), ], session: { strategy: "jwt" }, // 其他配置... });
2. 前端发起不同类型的登录请求
使用signIn函数的第三个参数传递自定义标识,区分基础登录和权限授权:
// 基础登录(仅获取用户信息) await signIn("google"); // 请求Google Ads权限授权 await signIn("google", undefined, { askForGoogleAdsPermissions: "true" });
3. 强制更新数据库中的账户Scope
NextAuth的Prisma Adapter默认不会自动更新已存在的Account记录的Scope,需在signIn回调中手动处理更新:
// 在[...nextauth].ts的callbacks中添加 callbacks: { async signIn({ account, user }) { if (!account) return false; // 查找用户已有的Google账户记录 const existingAccount = await prisma.account.findFirst({ where: { userId: user.id, provider: "google", providerAccountId: account.providerAccountId, }, }); if (existingAccount) { // 更新账户的Scope、Token等信息 await prisma.account.update({ where: { id: existingAccount.id }, data: { scope: account.scope, access_token: account.access_token, refresh_token: account.refresh_token, expires_at: account.expires_at, }, }); } return true; }, // 在JWT回调中同步更新Scope信息,确保Session中能获取到最新权限 async jwt({ token, account }) { if (account) { token.accessToken = account.access_token; token.refreshToken = account.refresh_token; token.expiresAt = account.expires_at; token.scope = account.scope; } return token; }, // 同步Session中的权限信息 async session({ session, token }) { if (token) { session.accessToken = token.accessToken; session.refreshToken = token.refreshToken; session.expiresAt = token.expiresAt; session.scope = token.scope; } return session; }, },
4. API调用前验证权限
在调用Google Ads API前,先从Session中校验是否已获取对应权限:
// 示例API路由 import { getServerSession } from "next-auth/next"; import { authOptions } from "./[...nextauth]"; export default async function handler(req, res) { const session = await getServerSession(req, res, authOptions); if (!session) return res.status(401).json({ error: "未授权" }); // 检查是否包含Google Ads权限 const hasAdsPermission = session.scope?.includes("https://www.googleapis.com/auth/adwords"); if (!hasAdsPermission) { return res.status(403).json({ error: "缺少Google Ads权限,请先关联账户" }); } // 执行Google Ads API调用逻辑 // ... }
内容的提问来源于stack exchange,提问作者Shawn
相关产品推荐
相关产品推荐

