skip to Main Content

I’m implementing Azure B2C with an ASP.NET MVC app on .NET 4.8. Our process uses cookie authentication and local user accounts with a custom flow policy, which works great.

The issue is that I am having trouble signing out of Azure B2C. Currently I’m testing locally and a user click the "Sign Out" button. The signout button clears the local data, and then redirects to the link via this document.

https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect?ref=blog.bajonczak.com#send-a-sign-out-request

The link appears to work successfully, but when I click the "Back" button in my browser, the user is still authenticated through B2C.

Can anyone tell me if this is the correct way to sign out of B2C?

Sign in code through Startup

public void ConfigureAuth(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        // ASP.NET web host compatible cookie manager
        CookieManager = new SystemWebChunkingCookieManager(),
        CookieName = "MyPortal.AuthCookie",
        CookieSameSite = Microsoft.Owin.SameSiteMode.Lax,
        CookieSecure = CookieSecureOption.Always,
        CookieHttpOnly = true,
        CookiePath = Globals.ApplicationRelativePath
    });

    app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            // Generate the metadata address using the tenant and policy information
            MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.TenantId, Globals.DefaultPolicy),

            // These are standard OpenID Connect parameters, with values pulled from web.config
            ClientId = Globals.ClientId,
            RedirectUri = Globals.RedirectUri,
            PostLogoutRedirectUri = Globals.PostLogoutRedirectUri,
            UseTokenLifetime = true,

            // Add the ProtocolValidator property here
            // Specify the callbacks for each type of notifications
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                RedirectToIdentityProvider = OnRedirectToIdentityProvider,
                AuthenticationFailed = OnAuthenticationFailed,
                SecurityTokenValidated = OnSecurityTokenValidated
            },

            // Specify the claim type that specifies the Name property.
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                RoleClaimType = "extension_Role",
                ValidateIssuer = false
            },

            // Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
            Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",

            // ASP.NET web host compatible cookie manager
            CookieManager = new SystemWebCookieManager(),
        }
    );
}

Log off function – added OWIN sign out for extra checks but it does the same thing regardless

public ActionResult LogOff()
{
    // Log out through OWIN 
    IEnumerable<AuthenticationDescription> authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes();
    HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray());

    Request.GetOwinContext().Authentication.GetAuthenticationTypes();
    HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);

    // Expire cookie
    if (Request.Cookies["MyPortal.AuthCookie"] != null)
    {
        HttpCookie authCookie = new HttpCookie("MyPortal.AuthCookie");
        authCookie.Expires = DateTime.Now.AddDays(-1d);
        Response.Cookies.Add(authCookie);
    }

    try
    {
        AccountManager accountManager = new AccountManager();
        accountManager.LogOff();

        var RequestUri = new System.Uri(Globals.AadLogoutUrl);
        return Redirect(RequestUri.ToString());
    }
    catch
    {
        throw;
    }
}

Finally – my RequestURI

https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{flow}/oauth2/v2.0/logout?post_logout_redirect_uri={redirectURI}

The User Identity repopulates itself when clicking back, opening a new tab or visiting the page again, or even opening a fresh browser and visiting the page. (all while the current browser still is open)

User Identity after logout

This is what it looks like when I hit the Index of the MVC site again.
User identity when visiting page again

2

Answers


  1. Chosen as BEST ANSWER

    I've been wrapping my head around this for awhile and haven't found a solution, other than a pretty hacky one.

    My setup is azure b2c, .NET 4.8, IIS 8.5 implementation. My signout appeared to work with my above code but when I could visit the session again in the same browser, or click the back button, the user was still logged in.

    The below code is what gets hit when the user opens a browser or hits a back button. It starts a new session and then the existing user is set to NULL, instead of an empty string and the cookies are present. Clearing the claims from the user and the cookies in this block of code cleared it.

    This is still a very hacky solution so I'm looking for another solution that is more sustainable.

    protected void Session_start()
    {
        // starting a session and already authenticated means we have an old cookie
        var existingUser = System.Web.HttpContext.Current.User;
        if (existingUser != null && existingUser.Identity.Name != "")
        {
            // clear any existing cookies
            IAuthenticationManager authMgr = System.Web.HttpContext.Current.GetOwinContext().Authentication;
            authMgr.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            //manually clear user from HttpContext so Authorize attr works
            HttpContext.Current.User = new ClaimsPrincipal(new ClaimsIdentity());
        }
    
    }
    

  2. It could be one of two things.

    1. User Session

    Although you have invalidated the cookie, there will likely still be an in-memory session on the server.

    In your LogOff action, clear the session to remove it from memory.

    Session.Clear()
    

    2. Browser Cache

    When you click the back button, depending on what browser you’re using, you might simply have cached content displayed. This may appear as though you’re signed in, but refreshing the page, or retyping your web app’s address, will often produce different results.

    Your post-logout redirect URI should always be set to a clean state route or action on your controller. For example, to a post-logout https://your-app.com/signedOut where you can perhaps redirect to the home page, and optionally display a logged out message for a few seconds.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search