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

集成OAuth2的Google Workspace日历会议插件重定向时CORS失败

Google Apps Script OAuth2 重定向到usercallback时CORS错误解决方案

问题背景

我用App Script和OAuth2库构建了Google Workspace日历会议插件,OAuth客户端配置如下:

return (
    OAuth2.createService("OAuthApp")
      .setAuthorizationBaseUrl(`${FRONTEND_DOMAIN}/auth/login`)
      .setTokenUrl(`${BACKEND_DOMAIN}/v1.0/oauth/exchange-code-for-token`)
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)
      .setCallbackFunction("authCallback")
      .setPropertyStore(PropertiesService.getUserProperties())
      .setCache(CacheService.getUserCache())
      .setLock(LockService.getUserLock())
);

用户在前端登录页面完成验证后,后端通过302重定向到插件的https://script.google.com/macros/d/{SCRIPT_ID}/usercallback地址并携带授权码,但此时浏览器控制台报CORS错误:

Access to XMLHttpRequest at 'https://script.google.com/macros/d/{SCRIPT_ID}/usercallback?code=xxx' (redirected from 'https://{BACKEND_ENDPOINT}/oauth/verify-otp') from origin 'https://{FRONTEND_ENDPOINT}' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

插件清单已配置必要的OAuth权限和Web App设置,尝试修改Content-Type避免预检请求无效,期望完成授权码与令牌的交换。

原因分析

Google Script的usercallback端点不支持CORS跨域AJAX请求,它的设计目标是接收浏览器直接导航的请求,而非AJAX响应的重定向。当前流程中,前端通过AJAX请求后端,后端返回302后浏览器会以AJAX方式发起重定向请求,触发预检OPTIONS请求,但Google未在该端点设置允许跨域的响应头,导致CORS拦截。

解决方案

1. 改用浏览器弹窗完成授权流程

放弃AJAX请求,在插件中通过新弹窗/标签页打开前端登录页面,让浏览器直接导航完成授权:

function initAuthFlow() {
  const authService = getOAuthService(); // 你的OAuth服务获取函数
  const authUrl = authService.getAuthorizationUrl();
  window.open(authUrl, 'AuthPopup', 'width=600,height=700,toolbar=no');
}

前端登录页面需要保留OAuth2库自动生成的state参数(会包含在authUrl中),验证成功后传递给后端。

2. 后端重定向时携带完整参数

后端验证用户身份后,必须将code和state两个参数一起拼接到重定向URL中,确保OAuth2库能验证请求合法性:

# 示例:后端(Flask)重定向逻辑
@app.route("/oauth/verify-otp")
def verify_otp():
    # 执行OTP验证逻辑...
    auth_code = generate_authorization_code()
    state = request.args.get("state")  # 从前端登录页面URL获取state
    callback_url = f"https://script.google.com/macros/d/{SCRIPT_ID}/usercallback?code={auth_code}&state={state}"
    return redirect(callback_url, code=302)

3. 完善authCallback函数

确保插件的回调函数正确处理授权响应,完成令牌交换:

function authCallback(request) {
  const service = getOAuthService();
  const isAuthorized = service.handleCallback(request);
  
  if (isAuthorized) {
    // 授权成功,自动关闭弹窗返回插件
    return HtmlService.createHtmlOutput(`
      <html>
        <body>
          <p>授权成功!请关闭此窗口返回日历插件。</p>
          <script>window.close();</script>
        </body>
      </html>
    `);
  } else {
    return HtmlService.createHtmlOutput("<p>授权失败,请重试。</p>");
  }
}

4. 验证Web App配置

确认插件的appsscript.json中Web App设置正确:

"webapp": {
  "access": "ANYONE",
  "executeAs": "USER_ACCESSING"
}

同时确保SCRIPT_ID与插件的实际ID一致。

注意事项

  • 禁止用AJAX发起授权流程,必须通过浏览器直接导航(弹窗/新标签页);
  • state参数必须完整传递,OAuth2库依赖它防止CSRF攻击,缺失会导致授权失败;
  • 后端重定向必须是302状态码的浏览器跳转,不能是AJAX响应中的重定向指令。

内容的提问来源于stack exchange,提问作者Gurbaj Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 01:20:20