如何通过Azure AD B2C混合流获取客户端已登录用户的access_token?
问题:Azure AD B2C保护的ASP.NET Core应用客户端获取已登录用户的Access Token
场景描述
我有一个受Azure AD B2C认证保护的ASP.NET Core 7 Web应用,服务端认证功能正常,可通过AuthorizeForScopes属性和ITokenAcquisition的GetAuthenticationResultForUserAsync方法获取access_token。现在需要在客户端获取用户的access_token,以便直接用JavaScript调用Web API,无需用户重新登录。
当前客户端代码
<script src="https://alcdn.msauth.net/browser/2.5.1/js/msal-browser.js"></script> <script type="module"> const msalInstance = new msal.PublicClientApplication({ auth: { clientId: "bbd0f8e1-a81a-43c6-82d8-xxxxxx", authority: "https://login.microsoftonline.com/cfc1d2e8-3626-4dca-9cd7-xxxxxx", }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: false, } }); const silentRequest = { scopes: ["https://xxxxxx.onmicrosoft.com/webapi/access_as_user"], loginHint: "me@email.com" }; const loginResponse = await msalInstance.ssoSilent(silentRequest); </script>
配置与错误信息
- Azure AD B2C配置:已勾选“管理>认证>隐式授权和混合流>ID令牌(用于隐式和混合流)”
- 问题:
ssoSilent方法已被废弃,运行代码时出现错误:Unsafe attempt to initiate navigation for frame with origin xxx to the /authorize endpoint,代码尝试重新认证用户而非从Cookie获取会话信息。
解决方案
一、错误原因分析
ssoSilent方法已被MSAL官方废弃,存在会话同步问题- Azure AD B2C隐式流配置不全,未勾选访问令牌(仅勾选了ID令牌,无法获取access_token)
- MSAL实例的authority格式错误(Azure AD B2C应使用
b2clogin.com域名,而非login.microsoftonline.com) - 未先检查本地缓存中的已登录账户,直接发起静默请求导致触发导航
二、修正后的客户端实现(使用MSAL.js v3.x)
1. 调整Azure AD B2C配置
在应用注册的认证>隐式授权和混合流中,同时勾选:
- ID令牌(用于隐式和混合流)
- 访问令牌(用于隐式流)
2. 更新MSAL.js代码
<script src="https://alcdn.msauth.net/browser/3.14.0/js/msal-browser.min.js"></script> <script type="module"> // 初始化MSAL实例,注意B2C的authority格式 const msalInstance = new msal.PublicClientApplication({ auth: { clientId: "bbd0f8e1-a81a-43c6-82d8-xxxxxx", // 替换为你的B2C租户、租户ID/域名和用户流名称 authority: "https://your-tenant.b2clogin.com/your-tenant.onmicrosoft.com/B2C_1_signupsignin", redirectUri: window.location.origin // 需与Azure AD B2C配置的重定向URI一致 }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: true // 跨域或旧浏览器场景建议开启 } }); async function getAccessToken() { let accessToken = null; // 先检查本地缓存中是否存在已登录账户 const accounts = msalInstance.getAllAccounts(); if (accounts.length > 0) { const request = { scopes: ["https://xxxxxx.onmicrosoft.com/webapi/access_as_user"], account: accounts[0] // 使用已缓存的账户发起请求 }; try { // 使用推荐的acquireTokenSilent替代ssoSilent const response = await msalInstance.acquireTokenSilent(request); accessToken = response.accessToken; console.log("成功获取Access Token:", accessToken); // 在此处调用你的Web API } catch (error) { // 静默请求失败(比如Token过期),触发交互式请求 if (error instanceof msal.InteractionRequiredAuthError) { try { const response = await msalInstance.acquireTokenPopup(request); accessToken = response.accessToken; } catch (popupError) { console.error("弹出式认证失败:", popupError); } } else { console.error("获取Token失败:", error); } } } else { console.error("未检测到已登录账户,请先通过服务端完成认证"); } return accessToken; } // 调用函数获取Token getAccessToken(); </script>
三、更简单的替代方案:服务端传递Token到客户端
如果不需要客户端自主刷新Token,可直接在服务端获取Token后传递到视图:
服务端控制器代码
using Microsoft.Identity.Web; using Microsoft.AspNetCore.Mvc; public class HomeController : Controller { private readonly ITokenAcquisition _tokenAcquisition; public HomeController(ITokenAcquisition tokenAcquisition) { _tokenAcquisition = tokenAcquisition; } public async Task<IActionResult> Index() { // 获取指定 scope 的 access_token var authResult = await _tokenAcquisition.GetAuthenticationResultForUserAsync( new[] { "https://xxxxxx.onmicrosoft.com/webapi/access_as_user" }); ViewData["AccessToken"] = authResult.AccessToken; return View(); } }
客户端视图代码
<script> // 从服务端视图数据中获取Token const accessToken = '@ViewData["AccessToken"]'; // 使用Token调用Web API示例 async function callWebApi() { const response = await fetch("https://your-web-api-url/api/resource", { headers: { "Authorization": `Bearer ${accessToken}` } }); const data = await response.json(); console.log("API响应:", data); } callWebApi(); </script>
内容的提问来源于stack exchange,提问作者fred_
相关产品推荐
相关产品推荐

