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.
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)
This is what it looks like when I hit the Index of the MVC site again.
2
Answers
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.
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.
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.