WS-Federation Single Logout (SLO)

By | September 18, 2018

WS-Federation Single Logout (SLO) is not supported by the Microsoft WIF libraries. This is mentioned in the Programming Windows Identity Foundation book of Vitorrio Bertocci on page 121. This should however only affect you if you have written your own IDP / IP-STS. As a result one can only sign-out from a single site at a time even though WS-Federation supports Single Sign-On (SSO). 

To get around this Vitorrio makes some suggestions on how to get around the problem. I have taken his sample on page 121 and adapted it. The work around consists out of 2 parts.

Register the RP.

Every time a Relay Party (RP) signs-in we record and store the RP url in a cookie. The RegisterRP method must be called before the sign-in completes.

public static void RegisterRP()
{
HttpCookie customAuthCookie = HttpContext.Current.Request.Cookies["UserRPs"];
string currentRP = HttpContext.Current.Request.QueryString["wreply"];
string existingRPs = customAuthCookie != null ? string.Format("{0},{1}", customAuthCookie.Value, currentRP) : currentRP;

if (!string.IsNullOrWhiteSpace(currentRP) && !string.IsNullOrWhiteSpace(existingRPs))
{
if (customAuthCookie == null)
{
string configPath = "system.web/httpCookies";
System.Web.Configuration.HttpCookiesSection httpCookiesSection = (System.Web.Configuration.HttpCookiesSection)ConfigurationManager.GetSection(configPath);
string domain = httpCookiesSection.Domain;
customAuthCookie = new HttpCookie("UserRPs");
customAuthCookie.Domain = httpCookiesSection.Domain;
customAuthCookie.HttpOnly = true;
customAuthCookie.Secure = true;
}

customAuthCookie.Value = existingRPs;
HttpContext.Current.Response.AppendCookie(customAuthCookie);
}
}

 

Global Log out

Once a log out request is received we retrieve the cookie with RP details and we generate sign out strings for each. The following part is a bit of hack but it works (this is per the book). We generate img literals for each RP with the signout requests as a result when the page loads and the image tags are loaded the client is logged out of all the RP’s. The last step is to redirect back to the RP that initiated the call. This last request should end up redirecting the user back to the login screen as he is now logged out.


public static void GlobalLogout(Page page)
{
IEnumerable rps = UserLoggedInRPs();
List signOutUrls = rps.Select(rp => string.Format("{0}?wa=wsignoutcleanup1.0", rp)).ToList();
IEnumerable signoutControls = signOutUrls.Select( rpUrl => new LiteralControl(string.Format("<img title="Signout request" src="{0}" />", rpUrl)));

foreach (LiteralControl signoutControl in signoutControls)
{
page.Controls.Add(signoutControl);
}

HttpCookie customAuthCookie = HttpContext.Current.Request.Cookies["UserRPs"];
if (customAuthCookie != null)
{
customAuthCookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.AppendCookie(customAuthCookie);
}

var requestMessage = (SignOutRequestMessage)WSFederationMessage.CreateFromUri(HttpContext.Current.Request.Url);
FederatedPassiveSecurityTokenServiceOperations.ProcessSignOutRequest(requestMessage, (System.Security.Claims.ClaimsPrincipal)HttpContext.Current.User, null, HttpContext.Current.Response);

String scriptText = string.Format("window.location = '{0}'",requestMessage.Reply);

page.ClientScript.RegisterClientScriptBlock(page.GetType(), "redirect", scriptText, true);
}

private static IEnumerable UserLoggedInRPs()
{
HttpCookie customAuthCookie = HttpContext.Current.Request.Cookies["UserRPs"];
if (customAuthCookie != null)
{
string existingRPs = customAuthCookie.Value;
return existingRPs.Split(',');
}

return new List();
}

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of