Next.js集成Google One Tap登录报错:IdentityCredentialError求助
Next.js Google One Tap登录报错排查与解决
问题现象
在Next.js应用中实现Google One Tap登录时,弹窗可正常显示Gmail账号列表,但点击登录后弹窗提示:Can't continue with google.com. Something went wrong.
浏览器控制台出现以下错误:
- The fetch of the id assertion endpoint resulted in a network error: ERR_FAILED
- The provider's token fetch resulted in an error response code.
- [GSI_LOGGER]: FedCM get() rejects with IdentityCredentialError: Error retrieving a token.
实现代码(GoogleOneTap.tsx)
/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import { useEffect, useCallback, useState } from "react"; import { signIn, useSession } from "next-auth/react"; import Script from "next/script"; declare global { interface Window { google: { accounts: { id: { initialize: (config: any) => void; prompt: (callback: (notification: any) => void) => void; cancel: () => void; revoke: (hint: string, callback: () => void) => void; }; }; }; } } export default function GoogleOneTap() { const { data: session } = useSession(); const [isGoogleScriptLoaded, setIsGoogleScriptLoaded] = useState(false); const handleCredentialResponse = useCallback((response: any) => { signIn("credentials", { credential: response.credential, redirect: false, }).catch((error) => { console.error("Error signing in:", error); }); }, []); const initializeGoogleOneTap = useCallback(() => { if (window.google) { try { window.google.accounts.id.initialize({ client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, callback: handleCredentialResponse, context: "signin", ux_mode: "popup", auto_select: false, }); window.google.accounts.id.prompt((notification: any) => { if (notification.isDismissedMoment()) { console.log( "One Tap was dismissed:", notification.getDismissedReason() ); } }); } catch (error) { if ( error instanceof Error && error.message.includes( "Only one navigator.credentials.get request may be outstanding at one time" ) ) { console.log( "FedCM request already in progress. Waiting before retrying..." ); setTimeout(initializeGoogleOneTap, 1000); } else { console.error("Error initializing Google One Tap:", error); } } } }, [handleCredentialResponse]); useEffect(() => { if (isGoogleScriptLoaded) { initializeGoogleOneTap(); } }, [isGoogleScriptLoaded, initializeGoogleOneTap]); useEffect(() => { if (session) { window.google?.accounts.id.cancel(); } }, [session]); return ( <Script src="https://accounts.google.com/gsi/client" async defer onLoad={() => setIsGoogleScriptLoaded(true)} strategy="afterInteractive" /> ); }
已排查项
- Google开发者控制台已添加
http://localhost和http://localhost:3000到授权JavaScript来源 - Google Client ID配置正确
- Chrome浏览器已启用第三方登录,无拦截器
解决方案
1. 完善NextAuth.js Credentials提供者配置
当前代码调用signIn("credentials"),但缺少对应的凭证验证逻辑。需要在[...nextauth].ts中配置Credentials提供者,验证Google返回的ID Token:
import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { OAuth2Client } from "google-auth-library"; const googleClient = new OAuth2Client(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID); export const authOptions = { providers: [ Credentials({ name: "Google One Tap", credentials: { credential: { type: "text" }, }, async authorize(credentials) { if (!credentials?.credential) return null; // 验证Google ID Token const ticket = await googleClient.verifyIdToken({ idToken: credentials.credential, audience: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, }); const payload = ticket.getPayload(); if (!payload) return null; // 返回用户信息给NextAuth return { id: payload.sub, name: payload.name, email: payload.email, image: payload.picture, }; }, }), ], }; export default NextAuth(authOptions);
需先安装依赖:npm install google-auth-library
2. 禁用FedCM解决冲突
控制台错误提到FedCM(联邦身份验证服务),可在One Tap初始化时禁用该功能:
window.google.accounts.id.initialize({ client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, callback: handleCredentialResponse, context: "signin", ux_mode: "popup", auto_select: false, // 添加此行禁用FedCM use_fedcm_for_prompt: false, });
3. 验证环境变量加载
在handleCredentialResponse中打印process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,确认客户端能正确读取到该值。
4. 避免重复初始化
添加状态标记是否已完成初始化,防止重复发起请求:
const [isInitialized, setIsInitialized] = useState(false); const initializeGoogleOneTap = useCallback(() => { if (window.google && !isInitialized) { try { // 初始化代码... setIsInitialized(true); } catch (error) { // 错误处理... } } }, [handleCredentialResponse, isInitialized]);
内容的提问来源于stack exchange,提问作者Ali Mazhar
相关产品推荐
相关产品推荐

