8 minute read

When building a Line Of Business (LOB) application, you are usually better off with implementing the customer's current Identity Provider (IdP) which could be ADFS, Azure AD or some others. The benefits are clear - users use a single account for all the services, authenticate through a central point, can be more protected by conditional access policies and as a great benefit, you can leverage the existing data through Microsoft Graph for example. So while it is obvious why to use Single Sign On in your application, a little bit less discussed topic is about Single Sign Out (SLO).

So while the benefits of using Single Sign On are obvious and there many articles about it, it way less discussed topic is Single Sign Out - the process of signing out the user from all web application which use the same IdP.

This, of course is most applicable with LOB applications, with most of the consumer applications, it would be a lot confusing - you sign in to Feedly with Facebook, then logout from Feedly - should you be signed out from Facebook as well? Or when you sign out from Facebook, should you be signed out from Feedly? Most of the users in consumer scenarios don't expect that.

Basic single sign out

With business applications, this is different. Take Office 365 for example. When you sign out from Exchange Online (OWA), you automatically get signed out from SharePoint Online and other Office 365 related services.

Now when you are making an LOB application, it should follow the same principle - when the user signs out from the application, he should be automatically signed out from Office 365 and other 3rd party applications. Achieving single sign out from your application and Office 365 (and AAD of course) is fairly simple, you simply redirect the user to a signout URL like this (specified as end_session_endpoint in the OIDC metadata):

https://login.microsoftonline.com/<tenant or common>/oauth2/logout?post_logout_redirect_uri=<optional_uri>

which signs them out from Azure AD (and delete the application cookies or session as well, so the user gets logged out from your application too) or simply call something like this from your ASP.NET code in the logout method:

await HttpContext.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
await HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

This is the very basic method of single sign out - user signed out from both your application and the IdP. This however doesn't get the user signed out from other 3rd party applications.

One of the nice examples of how SLO should be done is Visual Studio Team Services logout page, which logs you out from all possible instances:

This is achieved through something called front-channel SLO (I will write about it later).

Honorable mention for SLO implementation is SAML protocol, where for example Azure AD event implements the support as well. However, I don't want to spend much time with SAML and we will focus on modern authentication protocols - especially OpenID Connect, because that's where all the action's gonna be.

OpenID Connect Options (RFCs)

OpenID Connect itself currently presents us with three options for how the single sign out can be handled: front-channel, back-channel and session managament. I will go over all of those briefly below.

Front-channel

The front-channel (RFC) is a method which you would use when you have a regular server-side backend (in PHP, ASP.NET and similar). When editing your application's settings, you add a Logout URL which then gets embedded to the signout page of the IdP (usually by iframe). This is what happens when logging out from VSTS as well. This allows your server to delete the cookies set on client, delete the session etc.

You could for example handle it by simply calling in your controller for ending the session:

await HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

Note that you don't sign out OpenID Connect middleware, because the IdP has already performed the signout.

It is usually called an endsession route, since it only terminates the session at the server and doesn't redirect the user to the IdP logout.

Additionally, the request should also contain the session ID so you can make sure that the call is really valid and ment for the current user's session.

Update (November 2017): When using ASP.NET Core OIDC middleware, you don't have to create a custom route where you handle the Sign Out, but you can point the server to /signout-oidc path, which will get picked up by the middleware and the sign out will be handled for you (including session validation). You can take a look into the source for reference.

Back-channel

The back-channel (RFC) is quite similar like the front-channel, except it doesn't get called by the client from the logout page of the IdP. This means, that the IdP calls your endpoint and provides it with a JWT which you then validate and from the information provided, you invalidate the specified session.

Session managament

Last but not least is the session management (RFC). This was designed to be mostly used with Single Page Applications hence it uses JavaScript to validate the current session. You simply it the from the OIDC metadata. Inside the iframe a check is performed against user's session at the IdP and if it has changed (user logged out, logged in as another user, ...), your code gets notified back by postMessage, they should be sent to the end_session_endpoint from the metadata as well. This should be done periodically (the RFC sample has interval of 3 seconds). It is also worth noting, that this usually works alongside with the front-channel logout as well.

Doing this in Azure AD

The above is the explanation for what the OIDC RFCs specify, however let's take a look at how we can leverage this with Azure Active Directory:

At the time of writing this article, the best way of handling SLO in your application is by using the session management. That means that in every single page you render on the server, you include a tiny bit of JavaScript code and an iframe which in case of change redirect the user to the end session endpoint of your application.

While you can specify a Logout URL for your application, it doesn't seem to have any effect on the sign out process - it doesn't get triggered by either front-channel or back-channel calls. However, when you look into the code of the Azure AD logout page (CLICKING THIS WILL LOG YOU OUT: https://login.microsoftonline.com/common/oauth2/logout), there is some JavaScript code which suggests support of the front-channel coming to us (no idea when tho). Also note the highlighted line which sets the constant result to success :).

function TryCompleteSignout() {
    var signoutComplete = true;
    if (signoutComplete) {
        CompleteSignout();
    }
}
function CompleteSignout() {
    var statusSuccess = true;
    CompleteSignoutRender(statusSuccess);
}
function CompleteSignoutRender(signoutSuccessful) {
    signoutSuccessful = signoutSuccessful && true;

    if (!signoutSuccessful) {

        RenderSignoutFailure();

    } else {
        RenderSignoutSuccess();

        setTimeout('InitiatorRedirect()', 1000);
    }
}
function RenderSignoutSuccess() {
    User.UpdateLogo('', "You signed out of your account");
    var signoutStatusMessage = $('#SignOutStatusMessage');
    signoutStatusMessage.text("It\u0027s a good idea to close all browser windows.");
    signoutStatusMessage.show();
}
function RenderSignoutFailure() {
    User.UpdateLogo('', 'Hmm... we\u0027re having trouble signing you out.');
    var signoutStatusMessage = $('#SignOutStatusMessage');
    signoutStatusMessage.text("You may still be signed in to some applications. Close your browser to finish signing out.");
    signoutStatusMessage.show();
}
function WriteSignoutFailedCookie() {
    document.cookie = "SOS" + "=1; path=/";
}
function InitiatorRedirect() { }

So this is it, many happy single sign outs! Also if you are looking into implementing the SLO to your ASP.NET application, the official sample might come in handy.

Comments

Andres Garcia

As you mention, the below string logs users out of the app as well as SP Online. Is there a Logout URL that will only logout of the single application?

https://login.microsoftonline.com//oauth2/logout?post_logout_redirect_uri=

Jan Hajek

If you want to log the user out of single application, just simply destroy their session only within the application instead of signing them out from the Identity Provider.

Jan Hajek

According to Joonas’s blog - https://joonasw.net/view/aad-single-sign-out-in-asp-net-core it should be supported… But I haven’t played with it lately to be honest and still using the session management method.

Francisco Malafaia

Great article! I am trying to implement Single Logout on a javascript implementation and cannot get Azure AD B2C to call the Logout URL on the Application setting. Any chance you could share a code to accomplish this? Just 2 javascript pages, as 2 applications on Azure B2C, and one Tenant. This pages are not SPA, they do refresh the HTML from time to time.

Senthil

Hi Honza I would like to Call the Logout through backend channel for clearing the Azure Session. Do you have any thoughts ?

Sagar  Ghimire

Is there any way I can disable single logout? I have built an .net core app with azure ad authentication. Everything works fine but when I tried to log out from the application, it automatically logs you out of web based outlook.com.

Posted the question here https://stackoverflow.com/questions/66872791/disable-azure-active-directory-single-logout

CJMak

Hi Jan,

Thanks for the article.

Where a user simply logs out of a single app and not fully from azure ad the app will as you say destroy the session and in essence force the user to log on again, only to the app. The problem with this is that when the app then Challenges at azure it simply responds positively (assuming the user was able to log on previously or the user is logged into some other app that required an azure ad logon). Hence the user logging on is not forced to enter their details again.

I wonder what the point is in logging the user off if they can just walk straight back in by clicking the logon button and not needing to re-enter their details?

How would one manage a client-side timeout if they aren’t able to actually log a user out?

Best regards,

Chris

To submit comments, go to GitHub Discussions.