Blazor应用跨浏览器Azure B2C令牌异常:Chrome转Edge后认证失败
问题描述
我有一个基于.NET Blazor的应用,使用Azure B2C进行用户身份认证,遇到以下跨浏览器认证异常:
- 在Chrome浏览器打开
https://mywebsite.com - 受网络策略强制切换至Edge浏览器,显示Azure B2C登录界面,用户完成登录
- Azure日志显示认证成功,但应用未收到令牌,跳转至错误页面
https://mywebsite.com/MicrosoftIdentity/Account/Error - 错误截图:

核心问题:Windows强制跨浏览器(Chrome→Edge)时,Azure B2C认证流程中断,应用无法接收并验证令牌。
当前配置代码
Program.cs
builder.Services.AddScoped<HttpContextAccessor>(); builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAdB2C", options); options.Events = new OpenIdConnectEvents { OnRedirectToIdentityProvider = async ctxt => { await Task.Yield(); }, OnAuthenticationFailed = async ctxt => { await Task.Yield(); }, OnSignedOutCallbackRedirect = async ctxt => { ctxt.HttpContext.Response.Redirect(ctxt.Options.SignedOutRedirectUri); ctxt.HandleResponse(); await Task.Yield(); }, OnTicketReceived = async ctxt => { if (ctxt.Principal != null) { if (ctxt.Principal.Identity is ClaimsIdentity identity) { var colClaims = await ctxt.Principal.Claims.ToDynamicListAsync(); var IdentityProvider = colClaims.FirstOrDefault( c => c.Type == "http://schemas.microsoft.com/identity/claims/identityprovider")?.Value; var Objectidentifier = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value; var EmailAddress = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")?.Value; var FirstName = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")?.Value; var LastName = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname")?.Value; var AzureB2CFlow = colClaims.FirstOrDefault( c => c.Type == "http://schemas.microsoft.com/claims/authnclassreference")?.Value; var auth_time = colClaims.FirstOrDefault( c => c.Type == "auth_time")?.Value; var DisplayName = colClaims.FirstOrDefault( c => c.Type == "name")?.Value; var idp_access_token = colClaims.FirstOrDefault( c => c.Type == "idp_access_token")?.Value; } } await Task.Yield(); }, }; });
Appsettings.json
"AzureAdB2C": { "Instance": "https://{ Azure B2C Domain Name}.b2clogin.com/tfp/", "ClientId": "<Client-Id>", "Domain": "{ Azure B2C Domain Name}.onmicrosoft.com", "SignUpSignInPolicyId": "B2C_1_Blazorsigninsingup", "ResetPasswordPolicyId": "", "EditProfilePolicyId": "" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }
原因分析
跨浏览器跳转时,Chrome和Edge属于独立的浏览器进程,默认情况下:
- 认证流程中生成的
state参数(用于防CSRF攻击)存储在浏览器会话Cookie中,无法跨浏览器共享 - 应用使用内存缓存存储认证会话状态,切换浏览器后,新进程无法访问原会话的状态数据,导致应用无法验证Azure B2C回调请求的合法性,进而拒绝接收令牌
- 空的
OnAuthenticationFailed事件无法捕获具体错误,排查难度大
解决方案
1. 使用分布式缓存存储认证会话状态
将认证会话状态(如state、nonce)存储到分布式缓存(如Redis、SQL Server),确保跨浏览器/进程的请求能访问到相同的状态数据。
代码调整示例(Redis分布式缓存)
首先安装NuGet包:Microsoft.Extensions.Caching.StackExchangeRedis
在Program.cs中添加缓存配置并修改认证逻辑:
// 配置Redis分布式缓存(替换为你的Redis连接字符串) builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = builder.Configuration.GetConnectionString("Redis"); options.InstanceName = "AuthState-"; }); builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAdB2C", options); // 配置使用分布式缓存存储认证状态 var serviceProvider = builder.Services.BuildServiceProvider(); var dataProtection = serviceProvider.GetRequiredService<IDataProtectionProvider>(); var distributedCache = serviceProvider.GetRequiredService<IDistributedCache>(); options.StateDataFormat = new DistributedCacheDataFormat( dataProtection.CreateProtector("Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectMiddleware", OpenIdConnectDefaults.AuthenticationScheme, "v1"), distributedCache, new OptionsMonitor<DistributedCacheDataFormatOptions>(new DistributedCacheDataFormatOptions(), null, null)); // 完善错误捕获逻辑 options.Events = new OpenIdConnectEvents { OnAuthenticationFailed = async ctxt => { // 记录错误日志 var logger = ctxt.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>(); logger.LogError(ctxt.Exception, "Azure B2C认证失败"); // 自定义错误跳转 ctxt.Response.Redirect("/AuthError?message=" + Uri.EscapeDataString(ctxt.Exception.Message)); ctxt.HandleResponse(); await Task.Yield(); }, // 保留原有其他事件逻辑 OnRedirectToIdentityProvider = async ctxt => { await Task.Yield(); }, OnSignedOutCallbackRedirect = async ctxt => { ctxt.HttpContext.Response.Redirect(ctxt.Options.SignedOutRedirectUri); ctxt.HandleResponse(); await Task.Yield(); }, OnTicketReceived = async ctxt => { if (ctxt.Principal != null) { if (ctxt.Principal.Identity is ClaimsIdentity identity) { var colClaims = await ctxt.Principal.Claims.ToDynamicListAsync(); var IdentityProvider = colClaims.FirstOrDefault( c => c.Type == "http://schemas.microsoft.com/identity/claims/identityprovider")?.Value; var Objectidentifier = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value; var EmailAddress = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")?.Value; var FirstName = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")?.Value; var LastName = colClaims.FirstOrDefault( c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname")?.Value; var AzureB2CFlow = colClaims.FirstOrDefault( c => c.Type == "http://schemas.microsoft.com/claims/authnclassreference")?.Value; var auth_time = colClaims.FirstOrDefault( c => c.Type == "auth_time")?.Value; var DisplayName = colClaims.FirstOrDefault( c => c.Type == "name")?.Value; var idp_access_token = colClaims.FirstOrDefault( c => c.Type == "idp_access_token")?.Value; } } await Task.Yield(); }, }; });
2. 优化Azure B2C应用配置
- 在Azure B2C门户中,确保**回复URL(Redirect URI)**配置正确,包含
https://mywebsite.com/signin-oidc(OpenID Connect默认回调路径) - 检查令牌配置,确保颁发的JWT格式兼容Blazor应用的解析逻辑
3. 提前引导浏览器选择(可选)
如果网络策略允许,可在应用中提前检测浏览器类型,引导用户直接使用Edge打开,避免强制切换:
// 在Blazor组件的OnInitializedAsync中添加检测逻辑 protected override async Task OnInitializedAsync() { var httpContext = await HttpContextAccessor.HttpContext; var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); if (userAgent.Contains("Chrome") && !userAgent.Contains("Edg")) { NavigationManager.NavigateTo("/UseEdge"); } }
验证步骤
- 部署修改后的应用
- 在Chrome中打开应用,触发强制切换到Edge
- 完成登录后,检查是否成功跳转回应用且用户身份正常加载
- 查看应用日志,确认无认证失败记录
内容的提问来源于stack exchange,提问作者Aishwarya Balaji
相关产品推荐
相关产品推荐

