使用Sign In With Google获取Subscribe With Google所需AccessToken的问题及invalid_grant错误排查
看起来你在从旧版Google Sign In切换到SIWG(Sign In With Google)后,遇到了两个核心问题:一是passport-google-oidc默认没返回AccessToken,二是用回调里拿到的code调用getToken时出现了invalid_grant错误。我来帮你一步步排查解决:
一、为什么拿不到AccessToken?
passport-google-oidc是基于OpenID Connect协议实现的,它的核心是身份验证,默认会帮你完成code交换id_token、用户信息的流程,但你之前没用到正确的回调参数!
在passport-google-oidc的策略回调中,除了req、issuer、profile,还有一个**credentials参数**——里面就包含了你需要的accessToken、refreshToken等凭证!你之前的回调函数签名漏掉了这个参数,所以才没拿到AccessToken。
二、invalid_grant错误的核心原因:Code已被消耗
你尝试用回调里的code去调用oAuth2Client.getToken(code),这个操作本质上是重复使用了同一个授权code——而OIDC协议中的授权code是一次性有效的!
当passport-google-oidc执行验证流程时,它已经自动用这个code去Google服务器交换过用户信息和凭证了,所以当你再次用同一个code请求时,Google会返回invalid_grant,提示这个code已经失效。
三、正确的解决方案
1. 直接从passport回调中获取AccessToken
修改你的策略回调函数,添加credentials参数,直接拿到AccessToken:
const GoogleStrategy = require('passport-google-oidc'); const Config_Account_Passport = { Google : new GoogleStrategy({ clientID : '6...MyClientID0c...apps.googleusercontent.com', clientSecret : 'a...MyClientSecret...', callbackURL: "https://...UrlOfMyServer.../google/auth", passReqToCallback : true }, async function(req, issuer, profile, credentials, done) { // 新增credentials参数 try { // 获取用户基本信息 const email = profile.emails[0].value; const oauth_id = profile.id; // 直接从credentials中拿AccessToken const accessToken = credentials.accessToken; // 这里可以直接用accessToken调用SWG接口,不需要再处理code await checkEntitlements(user, accessToken); return done(null, user); } catch(e) { return done(e, false); } }) }; module.exports = Config_Account_Passport;
2. 修改checkEntitlements函数,直接使用AccessToken
既然已经拿到了AccessToken,就不需要再通过code去换取了,简化你的checkEntitlements逻辑:
const Payment_SwG = require('./../modules/Payment/SwG'); async function checkEntitlements(user, accessToken) { try { // 直接使用accessToken调用SWG的权限检查接口 const entitlements = await Payment_SwG.checkEntitlements(accessToken); // 处理权限信息,比如存入数据库或返回给前端 // ... } catch(e) { throw e; } }
3. 额外的配置检查
确保你的passport授权请求中,已经包含了SWG所需的scope和正确的参数:
passport.authenticate('google', { scope: [ 'openid', 'profile', 'email', 'https://www.googleapis.com/auth/subscribewithgoogle.publications.readonly' ], accessType: 'offline', // 确保能拿到refresh_token(如果需要长期授权) prompt: 'consent' // 强制用户授权,确保能拿到有效凭证(可选,首次授权时有用) });
四、补充说明
如果你需要长期保存授权(比如用户离线时也能检查权限),可以从credentials中拿到refreshToken,用它来刷新AccessToken,避免每次都要用户重新登录。
备注:内容来源于stack exchange,提问作者Gregor

