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

Google Docs插件登录后重定向问题:OAuth2集成报错排查

在Google Docs插件中集成OAuth2的重定向问题

我想在Google Docs插件中集成OAuth2,当前通过插件打开新窗口进行授权,但重定向回插件时遇到问题。我使用项目设置中的https://script.google.com/macros/s/Script_ID/exec作为redirectUri,重定向时出现以下错误:

Google Docs遇到错误。请尝试重新加载此页面,或稍后再访问。
如需了解更多Google Docs编辑器相关信息,请访问我们的帮助中心。
对于带来的不便,我们深表歉意。

  • Google Docs团队

错误截图

请问如何正确重定向到我的插件?以下是我的实现示例:

code.gs文件函数

function getAuthorizationUrl() {
  var a = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
  
  // Your app's Client ID from Azure Portal
  var clientId = "client_id";
  
  // The correct callback URL after user authorization
  var redirectUri = "https://script.google.com/macros/s/Script_Id/exec";
  
  // Scopes you are requesting access to (Microsoft Graph API scopes)
  var scope = "user.read mail.send openid profile email offline_access";
  
  // State and nonce for security
  var state = "12345";
  var nonce = "678910";

  // Assemble the full authorization URL
  var authUrl = a + "?client_id=" + clientId +
                "&response_type=code" + // Requesting an authorization code
                "&redirect_uri=" + encodeURIComponent(redirectUri) + // The new redirect URL
                "&scope=" + encodeURIComponent(scope) +
                "&response_mode=fragment" +
                "&state=" + state +
                "&nonce=" + nonce;

  return authUrl;
}

// Function to handle the callback and exchange the authorization code for an access token
function doGet(e) {
  var code = e.parameter.code; // Get the authorization code from the query parameters

  if (code) {
    // Proceed to exchange the authorization code for an access token
    // You can call a function to exchange the code for tokens and continue the flow
    return HtmlService.createHtmlOutput('Authorization complete! You can close this window.');
  } else {
    return HtmlService.createHtmlOutput('Error: Authorization failed or no code returned.');
  }
}

HTML文件代码

<button onclick="login()">Sign in with OAuth</button>

<script>
 function login() {
        // Get the OAuth URL from the server
        google.script.run.withSuccessHandler(function(authUrl) {
          // Open the OAuth dialog in a new window
          window.open(authUrl, '_blank', 'width=600,height=600');
        }).getAuthorizationUrl();
      }
</script>

问题分析与解决方案

核心问题

你遇到的错误根源是**response_mode=fragment的使用**:该模式会将授权码放在URL的片段(#后)中,而Google Apps Script的doGet函数无法读取URL片段参数,导致无法获取code,进而触发Docs页面错误。

修复步骤

  1. 修改response_mode为query
    在getAuthorizationUrl函数中,将&response_mode=fragment替换为&response_mode=query,这样授权码会通过URL查询参数传递,doGet可以正常获取code值。

  2. 验证重定向URI配置

    • 确保Google Apps Script项目已部署为Web应用,部署权限至少设置为“任何人,甚至匿名”(测试阶段);
    • 在Azure Portal的应用注册中,重定向URI必须与代码中的redirectUri完全一致(包括大小写、末尾的/exec)。
  3. 安全与逻辑优化

    • 动态生成state和nonce(不要用固定值),并在回调时验证state,防止CSRF攻击;
    • 授权完成后,让回调页面自动关闭并通知插件主窗口更新状态。

修改后的完整代码示例

code.gs

function getAuthorizationUrl() {
  var authEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
  
  // Azure Portal获取的客户端ID
  var clientId = "你的客户端ID";
  
  // Web应用部署后的回调URL
  var redirectUri = "https://script.google.com/macros/s/你的脚本ID/exec";
  
  // 请求的Microsoft Graph权限范围
  var scope = "user.read mail.send openid profile email offline_access";
  
  // 动态生成安全参数
  var state = Utilities.getUuid();
  var nonce = Utilities.getUuid();

  // 组装授权URL,使用response_mode=query
  var authUrl = authEndpoint + "?client_id=" + clientId +
                "&response_type=code" +
                "&redirect_uri=" + encodeURIComponent(redirectUri) +
                "&scope=" + encodeURIComponent(scope) +
                "&response_mode=query" +
                "&state=" + state +
                "&nonce=" + nonce;

  // 存储state用于后续验证
  PropertiesService.getUserProperties().setProperty("OAUTH_STATE", state);
  
  return authUrl;
}

function doGet(e) {
  var code = e.parameter.code;
  var state = e.parameter.state;
  var storedState = PropertiesService.getUserProperties().getProperty("OAUTH_STATE");

  // 验证state合法性
  if (!state || state !== storedState) {
    return HtmlService.createHtmlOutput('错误:非法请求,state验证失败。');
  }

  if (code) {
    var token = exchangeCodeForToken(code);
    
    if (token) {
      // 保存令牌到用户属性
      PropertiesService.getUserProperties().setProperty("ACCESS_TOKEN", token.access_token);
      PropertiesService.getUserProperties().setProperty("REFRESH_TOKEN", token.refresh_token);
      
      // 自动关闭窗口并通知插件授权成功
      return HtmlService.createHtmlOutput('<script>window.close(); window.opener.google.script.run.notifyAuthSuccess();</script>');
    } else {
      return HtmlService.createHtmlOutput('错误:换取访问令牌失败。');
    }
  } else {
    return HtmlService.createHtmlOutput('错误:授权失败或未返回授权码。');
  }
}

function exchangeCodeForToken(code) {
  var tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
  var clientId = "你的客户端ID";
  var clientSecret = "你的客户端密钥"; // 机密客户端需填写,公开客户端可省略
  var redirectUri = "https://script.google.com/macros/s/你的脚本ID/exec";
  
  var payload = {
    client_id: clientId,
    client_secret: clientSecret,
    code: code,
    redirect_uri: redirectUri,
    grant_type: "authorization_code"
  };
  
  var options = {
    method: "post",
    payload: payload,
    muteHttpExceptions: true
  };
  
  var response = UrlFetchApp.fetch(tokenEndpoint, options);
  var json = JSON.parse(response.getContentText());
  
  return json.access_token ? json : null;
}

// 通知插件授权成功
function notifyAuthSuccess() {
  // 可在此更新插件UI,比如显示已登录状态
  console.log("授权成功");
}

HTML文件

<button onclick="login()">使用OAuth登录</button>
<div id="status"></div>

<script>
 function login() {
        document.getElementById('status').textContent = "正在跳转授权...";
        google.script.run.withSuccessHandler(function(authUrl) {
          window.open(authUrl, '_blank', 'width=600,height=600');
        }).getAuthorizationUrl();
      }

 // 接收授权成功通知
 function notifyAuthSuccess() {
   document.getElementById('status').textContent = "授权成功!";
 }
</script>

内容的提问来源于stack exchange,提问作者M Јǚйáĩď

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 06:45:17