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

在Gmail Add-Ons中实现OAuth2隐式授权流的技术问询

我之前在做Gmail Add-On的时候刚好实现过OAuth2隐式授权流,结合你已经用到的StateTokenBuilder和/usercallback端点,给你梳理下完整的实现细节和关键代码,帮你打通整个流程:

一、核心流程梳理

你已经走通了第一步:用StateTokenBuilder生成安全的state参数,拼接出授权URL让用户跳转认证。接下来的关键是处理授权成功后的重定向——因为隐式授权的access_token是放在URL的hash部分,Apps Script的服务器端无法直接获取,得通过前端页面中转提取,再把token保存起来供后续使用。

二、关键代码实现

1. 生成带State的授权URL

确保把StateToken生成的参数拼到redirect_uri里,用来防止CSRF攻击:

function getAuthorizationUrl() {
  // 生成带回调方法的StateToken,有效期1小时
  const stateToken = ScriptApp.newStateToken()
    .withMethod('handleOAuthCallback')
    .withTimeout(3600)
    .createToken();

  // 拼接隐式授权的URL,注意response_type必须是token
  const authUrl = 'https://你的授权服务器域名/auth?' +
    'client_id=你的客户端ID' +
    '&response_type=token' +
    '&redirect_uri=' + encodeURIComponent(`https://script.google.com/macros/d/你的脚本ID/usercallback?state=${stateToken}`) +
    '&scope=你需要的权限列表';

  return authUrl;
}

2. 处理授权回调

因为access_token在URL hash里,需要用HtmlService创建一个临时页面提取参数,再传给服务器端保存:

function handleOAuthCallback(request) {
  // 用前端JS提取hash中的授权参数
  return HtmlService.createHtmlOutput(`
    <script>
      // 解析hash部分的参数
      const hashParams = new URLSearchParams(window.location.hash.slice(1));
      const accessToken = hashParams.get('access_token');
      const tokenType = hashParams.get('token_type');
      const expiresIn = hashParams.get('expires_in');

      // 调用服务器端函数保存token,成功后关闭页面
      google.script.run
        .withSuccessHandler(() => window.close())
        .saveOAuthTokens(accessToken, tokenType, expiresIn);
    </script>
  `);
}

// 把token保存到用户专属的Properties中
function saveOAuthTokens(accessToken, tokenType, expiresIn) {
  const userProps = PropertiesService.getUserProperties();
  userProps.setProperty('OAUTH_ACCESS_TOKEN', accessToken);
  userProps.setProperty('OAUTH_TOKEN_TYPE', tokenType);
  // 计算并保存过期时间戳,方便后续检查
  const expireTime = Date.now() + parseInt(expiresIn) * 1000;
  userProps.setProperty('OAUTH_EXPIRE_TIME', expireTime.toString());
}

3. 在Gmail Add-On中触发授权

在卡片里添加授权按钮,判断token状态(未授权/过期则显示按钮,已授权则展示业务内容):

function buildMainCard() {
  const userProps = PropertiesService.getUserProperties();
  const accessToken = userProps.getProperty('OAUTH_ACCESS_TOKEN');
  const expireTime = userProps.getProperty('OAUTH_EXPIRE_TIME');

  let card;
  // 检查token是否存在且未过期
  if (!accessToken || Date.now() > parseInt(expireTime)) {
    const authUrl = getAuthorizationUrl();
    card = CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('需要授权'))
      .addSection(CardService.newCardSection()
        .addWidget(CardService.newTextButton()
          .setText('点击完成授权')
          .setOpenLink(CardService.newOpenLink()
            .setUrl(authUrl)
            .setOpenAs(CardService.OpenAs.OVERLAY) // 用弹窗打开,体验更好
            .setOnClose(CardService.OnClose.RELOAD_ADD_ON)))) // 关闭后自动刷新插件
      .build();
  } else {
    // 已授权状态,展示你的业务逻辑内容
    card = CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('授权成功'))
      .addSection(CardService.newCardSection()
        .addWidget(CardService.newTextParagraph().setText('已获取有效访问令牌,可以开始使用功能啦')))
      .build();
  }
  return [card];
}
三、重要注意事项
  • 必须在你的OAuth应用后台,把https://script.google.com/macros/d/你的脚本ID/usercallback添加到允许的重定向URI列表里,否则授权服务器会拒绝跳转。
  • StateToken是必须的,它能有效防止CSRF攻击,Apps Script的/usercallback端点会验证这个参数的合法性,不要省略。
  • 隐式授权不会返回refresh_token,所以要提前监控token过期时间,过期后引导用户重新授权。
  • 用UserProperties保存token,每个用户的token是独立存储的,不会互相干扰。

内容的提问来源于stack exchange,提问作者Aakash Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:56:20