Azure B2C IDP直通内部令牌无法刷新,调用Graph API失败求助
解决Azure B2C IDP直通模式下内部AD令牌自动刷新问题
在Blazor WASM应用中使用Azure B2C的IDP直通功能,搭配多AD自定义策略配置内部身份提供商时,会遇到这个问题:外层B2C的JWT Bearer令牌过期后能被MSAL自动刷新,但内部AD返回的idp_access_token过期后,B2C不会自动更新它。这直接导致依赖该令牌调用Microsoft Graph API发送邮件的功能失败,甚至引发站点崩溃。
以下是具体解决方案:
一、调整Azure B2C自定义策略,支持内部IDP令牌刷新
- 在自定义策略的
RelyingParty节点中,添加idp_refresh_token到输出声明,确保B2C返回内部IDP的刷新令牌:
<RelyingParty> <DefaultUserJourney ReferenceId="SignUpOrSignIn" /> <TechnicalProfile Id="PolicyProfile"> <DisplayName>PolicyProfile</DisplayName> <Protocol Name="OpenIdConnect" /> <OutputClaims> <!-- 保留原有声明 --> <OutputClaim ClaimTypeReferenceId="idp_access_token" /> <OutputClaim ClaimTypeReferenceId="idp_refresh_token" /> <!-- 新增刷新令牌声明 --> </OutputClaims> <SubjectNamingInfo ClaimType="sub" /> </TechnicalProfile> </RelyingParty>
- 确保内部AD的技术配置文件维持IDP会话,在对应
TechnicalProfile中添加会话管理引用:
<TechnicalProfile Id="AADMultiTenant-OpenIdConnect"> <!-- 保留原有配置 --> <UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin" /> </TechnicalProfile>
二、通过MSAL.NET自动管理内部IDP令牌的刷新
不要直接从HttpContext.User的Claims中提取idp_access_token,而是利用MSAL的令牌缓存和On-Behalf-Of流程,让它自动处理令牌的获取与刷新:
修改后端API的令牌获取逻辑
using Microsoft.AspNetCore.Mvc; using Microsoft.Graph; using Microsoft.Identity.Client; using System.Threading.Tasks; namespace MyApp.Controllers { [ApiController] public class SampleController : Controller { private readonly IConfidentialClientApplication _msalClient; // 注入MSAL机密客户端实例(需在Program.cs中提前配置) public SampleController(IConfidentialClientApplication msalClient) { _msalClient = msalClient; } [HttpGet("/api/testsendmail")] public async Task<ActionResult> Send() { try { // 从请求头获取用户的B2C ID令牌 var b2cIdToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); // 发起On-Behalf-Of请求,获取内部AD的Graph API访问令牌 var result = await _msalClient.AcquireTokenOnBehalfOf( new[] { "https://graph.microsoft.com/Mail.Send" }, new UserAssertion(b2cIdToken)) .ExecuteAsync(); var graphClient = OnBehalfOfGraphClient.GetGraphClient(result.AccessToken); var mailRequest = graphClient.Me.SendMail( new Message() { ToRecipients = new[] { new Recipient() { EmailAddress = new EmailAddress() { Address = "test@nowhere.local" } } }, Subject = "Test", Body = new ItemBody() { ContentType = BodyType.Text, Content = "Test" } }, true).Request(); await mailRequest.PostAsync(); return Ok(); } catch (MsalUiRequiredException) { // 令牌无法静默刷新,需要用户重新认证 return Unauthorized(new { Message = "请重新登录以获取有效权限" }); } catch (ServiceException ex) { // 处理Graph API的其他业务错误 return StatusCode((int)ex.StatusCode, new { Message = ex.Message }); } } public static class OnBehalfOfGraphClient { public static GraphServiceClient GetGraphClient(string token) { var authProvider = new DelegateAuthenticationProvider(async (request) => { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); }); return new GraphServiceClient(authProvider); } } } }
在Blazor前端处理重新认证
当后端返回401时,触发MSAL的重新登录流程:
@inject MsalAuthenticationService MsalService <button @onclick="SendMail">发送测试邮件</button> @code { private async Task SendMail() { try { var response = await Http.GetAsync("/api/testsendmail"); if (!response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { // 跳转至B2C登录页面重新认证 await MsalService.LoginRedirect(new[] { "openid", "profile", "Mail.Send" }); } else { var errorMsg = await response.Content.ReadFromJsonAsync<dynamic>(); Console.WriteLine($"请求失败:{errorMsg.Message}"); } } } catch (Exception ex) { Console.WriteLine($"发送邮件出错:{ex.Message}"); } } }
三、关键说明
- On-Behalf-Of流程:通过用户的B2C ID令牌,向内部AD申请Graph API的访问令牌,MSAL会自动利用内部IDP的刷新令牌更新过期的
idp_access_token。 - 错误捕获:
MsalUiRequiredException表示无法静默刷新令牌,必须触发用户交互重新登录。 - 权限配置:确保内部AD的应用注册已授予
Mail.Send的委托权限,且B2C应用注册的权限范围包含该权限。
内容的提问来源于stack exchange,提问作者Simon M
相关产品推荐
相关产品推荐

