c# - Azure B2C Logout Implementation in ASP.NET Core MVC and ASP.NET MVC applications - Stack Overflow

admin2025-04-26  8

I'm currently working on multiple applications using different frameworks: two ASP.NET MVC applications on legacy .NET, and one ASP.NET Core MVC application. All of these applications use Azure AD B2C for authentication. Both .NET Framework applications implement Azure AD B2C using the OpenID Connect middleware, and they successfully share a single sign-on (SSO) experience.

However, I'm struggling to implement a consistent sign-out experience across these applications.

Specific Issues:

  1. Single Sign-Out Issue:
    The sign-out functionality in my ASP.NET Core application works correctly when used on its own. However, logging out from the ASP.NET Core application does not sign out the user from the .NET Framework applications, breaking the consistent single sign-out experience. This issue persists despite all applications using the same Azure AD B2C tenant and policies.

  2. .NET Framework Logout Implementation:
    In my ASP.NET MVC applications, I am using Request.GetOwinContext().Authentication.SignOut() to sign the user out and redirect them to the Azure AD B2C logout URL. While the URL is correct and redirection occurs as expected, it does not successfully sign the user out from the ASP.NET Core application.

What I've tried:

  1. Ensured that the logout URL used in the applications is configured correctly

  2. Double-checked the implementation of the sign-out logic, but the SSO experience remains inconsistent

  3. Implemented front-channel logout as recommended by Microsoft documentation, but this did not resolve the issue

ASP.NET Core MVC application's code:

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 application:

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/" + tenantName + ".onmicrosoft/" + 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);
                    }
        }
    });
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745656334a312471.html

最新回复(0)