IdentityServer4 – Global Logout
I’ve created a brand new, micro-services oriented platform at my current company. A key feature to this approach is integration of Single Sign-On. For this, I’ve adopted the wonderful, open-source project IdentityServer4.
The abridged version of the architecture is that the company creates multiple apps with api resources (a big inspiration is the whole Google API market). So, one team may be responsible for App A and another for App B, and so on. Each app is registered with the OAuth provider. Single sign-on allows for a user logging into the platform to login once to access all apps. It’s lovely.
However, it was a bit more challenging to get Single Sign-Out (or single log-out / SLO). Our apps are written in C# .Net Core, meaning we use an MVC pattern and are server based (as opposed to more javascript only browser based applications). As such, IdentityServer4 supports both Front Channel Logout and Back Channel Logout. I decided to move ahead with using front-channel logout.
Logging out from a single client was easy, but the challenge was killing the entire session AND telling all other clients who had active sessions that the user had logged out.
Here is how I set up my simple (but working!) solution.
Step 1: In each of your projects ensure that there are two logout functions, one for a UI logout and one for the front-channel logout. The front-channel logout is called by an iframe from IdentityServer4 when it ends the session (endSession endpoint).
In my setup, because all apps belong to the company, I have one single class that every controller inherits. This class pulls information about the user as needed, but also contains my two logout methods:
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var disco = await client.GetDiscoveryDocumentAsync(config.Value.authority);
return Redirect(disco.EndSessionEndpoint);
}
public async Task<IActionResult> FrontChannelLogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
var currentSid = User.FindFirst("sid")?.Value ?? "";
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
return NoContent();
}
The first logout action does not need any parameter as it is initiated by the user via a button click on the UI. This function removes the local cookie and logs the user out (using Identity) and then redirects the user to be logged out at the IdentityServer. The IdentityServer then takes care of logging the user out of all active sessions, but ONLY if a Front (or back) channel url is configured.
Step 2: Where you configure your clients (whether in a Config.cs or in database), within that client you want to point the FrontChannelLogoutUrl to the location of the front-channel logout, so that IdentityServer can call it with your session id (sid).
So for me, because all of my controllers inherit my OAuth class, I can call any controller for that project and add “frontchannellogout”. The sid is automatically passed by IdentityServer4 (though you can turn that off in a Configuration section).
For example, on the client configuration in the IdentityServer I have something like this:
new Client
{
ClientId = "my-client-id",
ClientName = "Hello world",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("mysecret".Sha256())
},
RedirectUris = Endpoints.redirectDomains,
PostLogoutRedirectUris =Endpoints.redirectDomains,
FrontChannelLogoutUri=Endpoints.BaseUrl+"/home/frontchannellogout",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"my-client-api"
},
AllowOfflineAccess = true
},
Every client would have this configured specific to itself. Now that this FrontChannelLogoutUri is configured, now when the user is logging out of IdentityServer, upon logout, an iframe is created where each FrontChannelLogout endpoint is called on each client application, effectively ending the session locally.
The user is now globally logged out of IdentityServer4.