Google OAuth2.0报Gaxios invalid_grant(Token过期/吊销)错误求助
报错根因判定
GaxiosError: invalid_grant 搭配错误描述 Token has been expired or revoked. 属于Refresh Token过期/被吊销导致的错误,和AccessToken无关:
- AccessToken默认有效期仅1小时,正常流程下OAuth2客户端会自动使用Refresh Token申请新的AccessToken,整个过程无感知,不会触发该报错
- 只有当Google服务端拒绝当前持有的Refresh Token的换票请求时,才会返回该错误。
对应解决方案
按出现概率从高到低排查修复即可:
- 问题1:OAuth Playground生成Refresh Token时未使用自有凭据
很多初学者生成Token时没有勾选Playground设置里的「Use your own OAuth credentials」选项,用Playground官方公共凭据生成的Refresh Token有效期只有几小时,到期自动作废。
修复:重新进入OAuth Playground,打开右上角设置面板,勾选自有凭据选项,填入你自己项目的Client ID、Client Secret,重新走完授权流程拿到新的Refresh Token,替换.env文件里的MAILING_REFRESH值。 - 问题2:GCP OAuth同意屏幕处于测试模式
测试状态下的OAuth应用生成的Refresh Token固定有效期为7天,到期自动失效。
修复:进入GCP控制台对应项目的OAuth同意屏幕页面,将应用状态修改为「发布」(个人自用应用不需要提交审核,填完必填字段直接发布即可),之后重新生成Refresh Token替换旧值。 - 问题3:Refresh Token被主动吊销或触达数量上限
如果你在Google账号安全中心手动撤销了对应应用的访问权限,或者同一个账号对同一个应用累计生成超过100个Refresh Token,最早生成的Token会被系统自动吊销。
修复:确认账号内对应应用的Gmail访问权限正常,重新生成Refresh Token替换即可,旧Token无需保留。 - 问题4:代码存在逻辑错误
你当前的代码有两处明显问题会加剧Token校验失败:- OAuth2构造函数传参顺序错误:第三个参数应该是重定向地址(也就是OAuth Playground的地址),你误将Refresh Token传到了这个位置
getAccessToken()是异步方法,同步调用拿不到有效的Token值;同时邮件HTML模板用了单引号,内部的${name}、${url}变量不会被解析,链接也没有正确替换变量
修复后的核心代码参考:
const nodemailer = require("nodemailer"); const { google } = require("googleapis"); const { OAuth2 } = google.auth; const oauth_link = "https://developers.google.com/oauthplayground"; const { EMAIL, MAILING_ID, MAILING_REFRESH, MAILING_SECRET } = process.env; // 修正构造函数传参顺序 const auth = new OAuth2( MAILING_ID, MAILING_SECRET, oauth_link ); // 改异步函数 exports.sendVerificationEmail = async (email, name, url) => { auth.setCredentials({ refresh_token: MAILING_REFRESH, }); // 等待异步返回有效access token const accessToken = await auth.getAccessToken(); const smtp = nodemailer.createTransport({ service: "gmail", auth: { type: "OAuth2", user: EMAIL, clientId: MAILING_ID, clientSecret: MAILING_SECRET, refreshToken: MAILING_REFRESH, accessToken: accessToken.token, // 取返回值的token字段 }, }); const mailOptions = { from: EMAIL, to: email, subject: "Facebook email verification", // 改用反引号包裹模板字符串,正确解析变量和链接 html: `<div><div style="max-width:500px;margin-bottom:1rem;align-items:center;display:flex"><img style="width:40px" src="https://seeklogo.com/images/F/facebook-icon-circle-logo-09F32F61FF-seeklogo.com.png" alt=""><span style="font-family:sans-serif;color:#3266a8;font-weight:700;padding-left:10px">Action Required : Activate your facebook account</span></div><div style="border-top:1px solid;border-bottom:1px solid;border-color:#deb887;padding:10px;padding-bottom:20px"><div style="height:35px"><span style="font-weight:700;font-family:sans-serif">Hello ${name}</span></div><div style="height:28px;font-family:sans-serif;padding-bottom:20px"><span>You recently created a profile on facebook. Please confirm your account</span></div><div style=""><a href="${url}" style="background-color:#3266a8;color:#fff;border-radius:10px;font-weight:700;font-family:sans-serif;text-decoration:none;font-size:15px;text-align:center;padding:9px;margin-bottom:1.5em">Confirm Your Account</a></div></div></div>`, }; return smtp.sendMail(mailOptions); };
额外提示:只要Refresh Token长期有效,客户端会自动处理AccessToken的刷新逻辑,不需要手动维护AccessToken值,你.env里配置的
MAILING_ACCESS字段实际没有用到,可以删掉。
内容的提问来源于stack exchange,提问作者sabari
相关产品推荐
相关产品推荐

