如何在单页应用中正确获取 Microsoft Graph 的访问令牌和刷新令牌

17次阅读

如何在单页应用中正确获取 Microsoft Graph 的访问令牌和刷新令牌

本文详解解决“cross-origin Token redemption is permitted only for the ‘single-page application’ client-type”错误的方法,重点说明为何前端 javascript 应用不能使用客户端密钥流,必须采用授权码流(pkce)或 msal 库实现安全的令牌获取。

该错误的根本原因在于:你正在前端 javaScript(即浏览器环境)中,尝试以“机密客户端”方式调用 /token 端点(如 Client Credentials Flow 或常规 Authorization Code Flow),但 azure AD 明确禁止跨域直接向 /token 接口提交 client_secret 或非 PKCE 增强的授权码——这仅允许 SPA 类型应用通过 PKCE 扩展的安全授权码流程完成令牌兑换。

✅ 正确做法:使用 PKCE 模式的授权码流程(Authorization Code Flow with PKCE)

Azure AD v2.0 要求所有公共客户端(包括 SPA)必须使用 PKCE(RFC 7636) 来防止授权码拦截攻击。这意味着:

  • 不能在前端 javascript 中直接发起 POST /token 请求并传入 client_secret(该字段对 SPA 无效且被拒绝);
  • 必须在重定向获取 code 后,用同一页面发起 POST /token 请求,并携带 code_verifier(与初始请求中的 code_challenge 匹配)。

? 示例:前端 js 中使用 fetch 完成 PKCE 令牌兑换(关键步骤)

// 1. 生成 code_verifier 和 code_challenge(推荐使用 crypto.subtle) async function generateCodeChallenge() {   const codeVerifier = Array.from({ length: 32 }, () =>      Math.floor(Math.random() * 26 + 65)   ).map(n => String.fromCharCode(n)).join('');    const encoder = new TextEncoder();   const data = encoder.encode(codeVerifier);   const hash = await crypto.subtle.digest('SHA-256', data);   const hashArray = Array.from(new Uint8Array(hash));   const hashBase64 = btoa(String.fromCharCode(...hashArray))     .replace(/+/g, '-').replace(///g, '_').replace(/=/g, '');    return { codeVerifier, codeChallenge: hashBase64 }; }  // 2. 构造 authorize URL(含 code_challenge & code_challenge_method) const { codeVerifier, codeChallenge } = await generateCodeChallenge(); const authUrl = `https://login.microsoftonline.com/{TenantID}/oauth2/v2.0/authorize` +   `?client_id={ClientId}` +   `&response_type=code` +   `&redirect_uri=https://your-app.com/auth-callback` +   `&scope=Mail.Read%20User.Read` +   `&code_challenge=${codeChallenge}` +   `&code_challenge_method=S256`;  // 3. 用户登录后重定向到 /auth-callback?code=xxx,再用 code + code_verifier 换 token: const tokenResponse = await fetch('https://login.microsoftonline.com/{TenantID}/oauth2/v2.0/token', {   method: 'POST',   headers: { 'Content-Type': 'application/x-www-form-urlencoded' },   body: new URLSearchParams({     client_id: '{ClientId}',     scope: 'Mail.Read User.Read',     code: '<>',     redirect_uri: 'https://your-app.com/auth-callback',     grant_type: 'authorization_code',     code_verifier, // ⚠️ 必须提供,且与之前生成的一致   }) });  const tokens = await tokenResponse.json(); console.log('access Token:', tokens.access_token); console.log('Refresh Token:', tokens.refresh_token); // ✅ SPA 可获得 refresh_token(需在 Azure AD 应用配置中启用“允许刷新令牌”)

✅ 注意:确保 Azure AD 应用注册中已满足以下条件:平台类型设置为 Single-page application (SPA);重定向 URI 填写为 https://your-domain.com/*(必须匹配前端实际回调地址);在“Authentication” → “Advanced settings”中勾选 ✅ Allow public client flows(启用 PKCE);(可选但推荐)开启 “Treat application as a public client” 并确认未填写 client_secret。

? 错误做法(导致报错的典型场景)

  • 在前端 JS 中硬编码 client_secret 并发送到 /token —— ❌ 浏览器无法保密密钥,Azure AD 直接拒绝;
  • 使用 response_type=code&grant_type=client_credentials 混搭流程 —— ❌ Client Credentials Flow 专用于后端服务,不接受跨域调用;
  • 应用注册平台误设为 “Web”,却在前端发起 /token 请求 —— ❌ Web 类型要求 client_secret,而浏览器环境不可用。

✅ 更优方案:使用官方 MSAL.js(推荐生产环境)

手动实现 PKCE 易出错。强烈建议集成 MSAL.js 2.x+(支持 Auth Code Flow + PKCE):

npm install @azure/msal-browser
import { PublicClientApplication } from '@azure/msal-browser';  const msalConfig = {   auth: {     clientId: '{ClientId}',     authority: 'https://login.microsoftonline.com/{TenantID}',     redirectUri: 'https://your-app.com/auth-callback'   } };  const msalInstance = new PublicClientApplication(msalConfig);  // 登录并获取令牌(自动处理 PKCE、缓存、静默刷新等) await msalInstance.loginPopup({   scopes: ['User.Read', 'Mail.Read'] });  const tokenResponse = await msalInstance.acquireTokenSilent({   scopes: ['User.Read', 'Mail.Read'] }); console.log('Access Token:', tokenResponse.accessToken); // MSAL 会自动管理 refresh_token 生命周期,无需手动调用 /token

? 提示:MSAL 默认启用 PKCE,且支持 acquireTokenSilent() 实现无感刷新,彻底规避手动 token 兑换风险。

总结

  • “Cross-origin token redemption…” 错误本质是身份验证流程与客户端类型不匹配
  • SPA 必须使用 Authorization Code Flow with PKCE,禁用 client_secret;
  • 手动实现需严格保证 code_verifier/code_challenge 一致性,并校验 Azure AD 应用配置;
  • 生产项目请优先采用 MSAL.js,它内建安全实践、自动刷新、错误恢复与最佳默认值。

遵循以上规范,即可安全、稳定地在浏览器中获取 Microsoft Graph 的访问令牌与刷新令牌。

text=ZqhQzanResources