ASP.NET Core与传统ASP.NET MVC的Azure AD B2C统一登出问题
Azure AD B2C跨ASP.NET与ASP.NET Core应用统一登出问题
我负责维护多个基于不同框架的应用:2个传统.NET的ASP.NET MVC应用,以及1个ASP.NET Core MVC应用,所有应用均采用Azure AD B2C进行身份验证。传统.NET应用通过OpenID Connect中间件对接Azure AD B2C,单点登录(SSO)功能正常,但实现跨应用统一登出时遇到以下问题:
核心问题
- 单点登出异常:ASP.NET Core应用自身登出功能正常,但从该应用登出后,传统.NET应用仍保持登录状态,破坏了统一的SSO登出体验,即便所有应用使用同一Azure AD B2C租户和策略。
- 传统.NET框架登出同步问题:在ASP.NET MVC应用中,使用
Request.GetOwinContext().Authentication.SignOut()执行登出并跳转至Azure AD B2C登出URL,URL正确且跳转正常,但无法同步登出ASP.NET Core应用。
已尝试的排查动作
- 确认所有应用的登出URL配置正确
- 反复检查登出逻辑实现,但SSO体验仍不一致
- 按照微软文档实现前端通道登出(front-channel logout),但问题未解决
相关代码
ASP.NET Core MVC应用代码
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration,Microsoft.Identity.Web.Constants.AzureAdB2C); builder.Services.Configure<CookieAuthenticationOptions>(options => { options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(5); }); builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignedOutCallbackPath = new PathString("/signout-callback-oidc"); options.Events.OnRedirectToIdentityProviderForSignOut = context => { var id_token_hint = context.Properties.Items.FirstOrDefault(x => x.Key == "id_token_hint").Value; if (id_token_hint != null) { context.ProtocolMessage.SetParameter("id_token_hint", id_token_hint); } return Task.CompletedTask; }; options.Events.OnAuthenticationFailed = context => { context.HandleResponse(); context.Response.Redirect("/Error/ErrorMessage?message=" + context.Exception.Message); return Task.CompletedTask; }; });
ASP.NET MVC应用代码
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); var env = (baseEnv.ToLower() == "live") ? "" : baseEnv; app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Cookies", CookieSecure = CookieSecureOption.Always, CookieHttpOnly = true, CookiePath = "/", ExpireTimeSpan = new TimeSpan(28,0,0,0,0), SlidingExpiration = true }); var baseUrlCleansed = $"{baseUrl.TrimEnd('/')}"; app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { MetadataAddress = "https://" + tenantName + ".b2clogin.com/" + tenantName + ".onmicrosoft.com/" + signinFlow + "/v2.0/.well-known/openid-configuration", ClientId = clientId, RedirectUri = $"{baseUrlCleansed}/", ResponseType = "id_token", PostLogoutRedirectUri = $"{baseUrlCleansed}/", Scope = "openid", SignInAsAuthenticationType = "Cookies", UseTokenLifetime = false, Notifications = new OpenIdConnectAuthenticationNotifications { SecurityTokenValidated = async n => { var id = n.AuthenticationTicket.Identity; id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken)); var claimsProcessor = new ClaimsProcessor(); await claimsProcessor.ProcessClaims(id); }, RedirectToIdentityProvider = n => { if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest) { var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token"); if (idTokenHint != null) { n.ProtocolMessage.IdTokenHint = idTokenHint.Value; } } return Task.FromResult(0); }, AuthenticationFailed = n => { var exception = n.Exception; if (exception is OpenIdConnectProtocolInvalidNonceException && exception.Message.Contains("IDX10316")) { n.HandleResponse(); n.Response.Redirect("/"); } if (exception.Message.StartsWith("OICE_20004") || exception.Message.Contains("IDX10311")) { n.SkipToNextMiddleware(); n.Response.Redirect("/"); } return Task.FromResult<object>(null); }, MessageReceived = n => { return Task.FromResult<object>(null); } } });
内容的提问来源于stack exchange,提问作者Hamza Ali
相关产品推荐
相关产品推荐

