Advanced ASP.NET Programming for Windows Identity Foundation

  • 9/15/2010

Single Sign-on, Single Sign-out, and Sessions

In this section, I’ll formalize some of the session-related concepts I’ve been hinting at so far. Namely, I’ll help you explore how WIF can reduce the number of times a user is prompted for credentials when browsing Web sites that are somehow related to each other. I’ll show you how you can sign out a user from multiple Web sites at once, making sure no dangling sessions are still open. Finally, I’ll share a few tricks you can use for tweaking the way in which WIF handles sessions.

Single Sign-on

In Chapter 3, I illustrated the dance that WS-Federation prescribes for signing in a relying party and how the WIF object model implements that. Let’s move the scenario a little further by supposing that you want to model the case in which the user visits more than one RP application.

If the RPs have absolutely nothing in common, there is not much to be said: every RP session will have its own independent story. But what happens if, for example, two RPs trust the same STS? Things get more interesting. Figure 4-4 briefly revisits the sign-in sequence, showing the user signing in the first RP application, named A.

Figure 4-4

FIGURE 4-4 The user signs in the RP named A, and in so doing it receives session cookies both from the STS and A

By now, you know the drill:

  1. The user sends a GET for a page on A.

  2. The user is redirected to the STS.

  3. The user is authenticated by whatever system the STS chooses and obtains a session cookie.

  4. The user gets back a token.

  5. The user sends the token to A and gets back a session cookie.

Here step 3 is especially interesting: In Figure 4-4, I assumed the authentication method picked by the STS involves the creation of a session with the STS site itself. That’s a reasonable assumption because that’s precisely the case with common authentication methods such as Kerberos (which leverages the session that the user created from her workstation at login time) or Forms authentication (which drops a session cookie, just like the WIF STS template does). If that is the case, at the end of the sign-in sequence the user’s machine will have two cookies: one representing the session with A, created by WIF, and one representing the session with the STS. Starting from that situation, let’s now look at Figure 4-5 to see what happens when the user signs in with B, another RP, that trusts the same STS.

Figure 4-5

FIGURE 4-5 The user signs in to the RP named B, and the existing session with the STS allows the user to sign in without being prompted for the STS credentials

The flow starts as usual, the user requests a page from B (step 1, as shown in Figure 4-5) and gets redirected to the STS to obtain a token (step 2). However, this time the user is already authenticated with the STS site because there is an active session represented by the STS cookie. This means the request for the STS page—say, Default.aspx if you are in the WIF STS template case—leads straight to execution of the SecurityTokenService issuing sequence without showing to the user any UI for credential gathering. The token is issued silently (step 3) and forwarded to B (step 4) according to the usual sequence. From the moment the user clicks on the link to B and the browser displays the requested page from B, only some flickering of the address bar in the browser will give away the fact that some authentication took place under the hood. That’s pretty much what Single Sign-on (SSO) means: the user went through the experience of signing in only once, and from that moment on the system is able to gain access to further RPs without prompting the user for credentials again.

SSO is an all-time favorite for end users. Using a single set of credentials for different Web sites without being reproached for it? Typing stuff only once? Count me in! This is also something that greatly pleases system administrators, because reducing the number of credentials to manage eases the administrative burden, lowers the probability that users will reuse the same password in different Web sites, and so on.

You’ll find that although most uninitiated people will not understand most of the stuff I covered in this book, everybody will have a clear, intuitive understanding and appreciation of SSO. Perhaps not surprisingly, SSO became the Holy Grail of the industry long before the emergence of claims-based identity, and as of today a lot of people think that the ultimate goal of identity management should be universal SSO.

The good news? As long as the STS creates a session in its authentication method, having SSO across Web site RPs protected via WIF is something that works right out of the box. There’s no arcane WS-Federation trick here, just good old cookies and a bit of trust management.

The hands-on lab ASP.NET Membership Provider and Federation (c:\IdentityTrainingKit2010\Labs\MembershipAndFederation) demonstrates how you can easily obtain SSO across Web sites using WIF. In fact, it shows how it is enough to add a page to an existing Web site, without modifying anything else, to add IP capabilities to it. The scenario in the lab modifies a Web site secured via the Membership provider, but this pattern can be applied to any authentication system.

Single Sign-out

In one of those rare instances in which building is easier than destroying, you are about to discover that Single Sign-out is somewhat harder to implement than Single Sign-on.

Single Sign-out, or SSOut, takes place when the termination of one session with a specific RP triggers the cleanup of state and other sessions across the same über session. In other words, signing out from one Web site cascades through all the Web sites that were part of the SSO club and signs out from them as well.

The mechanics of SSOut are not very straightforward, especially because the outcome of the entire process relies on all the entities involved receiving messages and complying. Both of those things are hard to enforce without reliable messaging or transactions; hence, the entire thing ends up being a “make your best effort” attempt. This state of affairs was well known to the authors of the WS-Federation specification, who were not especially prescriptive in describing the messages and mechanisms used for implementing SSOut. WIF does support SSOut out of the box for RPs, but the STS template is not especially thorough in implementing all its details. In this section, I’ll clue you in to the things you need to add for achieving more complete support.

Signing Out from One RP

Before getting into the details of how to handle signing out from multiple Web sites, let’s see what it takes to sign out from just one.

What keeps a user session alive, apart from the sheer Forms authentication machinery? First of all, it’s the existence (and validity) of the session cookie generated at sign-on time. The default name used by WIF for that cookie is FedAuth, with an additional FedAuth1…FedAuthn if the size of the SessionSecurityToken requires multiple cookies. You can easily take care of that yourself—it’s just a matter of calling FormsAuthentication.SignOut and deleting the session cookie (by hand or via SessionAuthenticationModule.DeleteSessionTokenCookie).

Second, it’s the session with the STS. If you delete the session with the RP but the user still has a valid session with the STS, she will still have access to the RP. The first unauthenticated GET elicits the usual redirect to the STS, and a valid session means that the user will be issued a new token without even being prompted for credentials.

The RP cannot directly change the STS session. In fact, it is not even supposed to know how that session (if any) is implemented to begin with! Luckily, WS-Federation defines a way for the RP to ask the STS to sign out the current principal. It will be up to the STS to decide what specific steps that entails in the context of its own implementation.

The mechanism that WS-Federation uses for signing out is straightforward: you are supposed to do a GET of the STS endpoint page with the parameter wa=wsignout1.0 and a wreply indicating where you want the browser to be redirected after the sign out is done. Once again, this is something you could do yourself; but why bother, when there is something that can take care of both the RP session cleanup and sending the sign-out message to the STS? That something is FederatedPassiveSignInStatus, an ASP.NET control that comes with WIF.

FederatedPassiveSignInStatus, as the name implies, can be used for easily displaying on your Web site the current state of the session. Drag it on any page, and its appearance will change according to whether you have a valid session in place. If you do, by default the control appears as a hyperlink with the text “Sign Out.” Clicking that link results in the current RP session being cleaned up. If the control property SignOutAction is set to FederatedSignOut, the control takes care of sending the wsignout1.0 message to the STS indicated in the SessionSecurityToken. Handy, isn’t it? That’s my favorite way of implementing sign out with WIF—it’s easy and painless.

Signing Out from Multiple RPs

From the perspective of the RP from which the user is signing out, cleaning up its own session and sending wsignout1.0 to the STS is all that is needed for closing the games. If there are other RPs with which the user still entertains an active session, it is responsibility of the STS to propagate the sign-out to them as well.

All that is left to do is for the other RPs to get rid of their sessions. Note that the STS already eliminated its own session with the user; hence, there is no risk of silent re-issuing after the other RPs do their cleanup.

Once again, WS-Federation provides a mechanism for that. I won’t go into the details here—it suffices to say that one way of requesting a cleanup to one RP is simply by doing a GET request on the RP and including in the query string the action wa=wsignoutcleanup1.0. You could specify an address via wreply to return to after the cleanup is done, but things can get problematic here. What if you have three RPs that need to clean up their sessions? If you are relying on the browser to perform the necessary GETs, you’d have to chain the requests. In addition to being complicated, this is a very brittle approach because something going wrong with one RP would jeopardize the chance of sending cleanup requests to all the subsequent RPs in the list. The STS can avoid using the browser and send the GET requests directly, but again, this is not very straightforward. For those reasons and others, the presence of a wreply is optional in wsignoutcleanup1.0 messages; it is acceptable to return something from the RP that somehow indicates the outcome of the operation. There’s more: the cleanup operation is required to be idempotent—that is, you should be able to call the same operation multiple times without affecting the outcome or raising errors. This allows you to retry the operation if you think something went wrong, without worrying about creating error situations.

Now for some good news: RPs secured via WIF handle wsignoutcleanup1.0 messages out of the box. The WSFAM looks out for those messages in its AuthenticateRequest handler. If the incoming message has a wsignoutcleanup1.0 action, WSFAM promptly deletes the session cookie and drops the corresponding token from the cache.

What sets apart the cleanup from all other actions I’ve described so far is that it might not end with a redirect. If the message contains a wreply, WSFAM dutifully returns a 302 message to the indicated location; if it doesn’t, it will return an image or .gif of a green check mark.

Returning the bits of one image upon successful cleanup is part of a clever strategy for working around the “chaining of sign-out redirects” problem described earlier. After the STS successfully clears its own session, it can return a page containing an <img> element for each RP whose session is up for cleanup. If the src value of the <img> elements is of the form https://RPAddress/Default.aspx?wa=wsignoutcleanup1.0, just rendering the list of images in the browser sends as many cleanup messages to the RPs in the list. Every successful cleanup sends back the image of the green check box, which the STS page can use for confirming that the sign-out actually took place for a given RP. Failure to render the image might be an indication that something went wrong with the cleanup operations.

All of the preceding activity relies on the fact that the STS will keep track of the RPs for which it issued a token in the context of one federated session. At sign-out time, the STS needs to remember the address of all RPs in order to generate the correct cleanup URIs for the src of the images collection in the sign-out page. The STS can use whatever state-preserving mechanism its owner sees fit. In my samples, I usually keep the list of RP URIs in a protected cookie because it requires zero state-management code on the server.

Did you get lost in all the back and forth required by the SSOut process? Let’s take a look at one example. Figure 4-6 illustrates the Single Sign-out message flow across two Web sites and a common STS, together with what happens to the client’s cookie collection as the sequence progresses.

Figure 4-6

FIGURE 4-6 A Single Sign-out process taking place as described in WS-Federation

Let’s examine every step. In the beginning, the user is signed in to WebSiteA and WebSiteB via tokens obtained from STS, and his browser is currently on WebSiteA. His cookie collection contains a FedAuth session cookie for each RP and one Forms authentication cookie (STSASPXAUTH) with STS. It also has an SsoSessions cookie with STS, which contains the list of RPs for which the STS issued a token in the context of its STSASPXAUTH session. Here’s how the process unfolds:

  1. The user clicks on a FederatedSignInStatus control instance on WebSiteA, triggering a POST in the authenticated session described by WebSiteA’s FedAuth cookie. The SignOutAction property of the control is set to FederatedPassiveSignOut.

  2. WebSiteA receives the request for signing out. As a result, it destroys its own session (by cleaning FedAuth from the WebSiteA cookie collection on the client) and redirects the browser to send a sign-out message to the STS that originated the current session.

  3. The browser follows the redirect, sending to the STS the sign-out message, along with the session cookie STSASPXAUTH and the cookie containing the list of RPs with whom the user might still entertain active sessions.

  4. The STS reacts by cleaning up all its cookies and sends back a page that contains images whose src URIs are in fact cleanup messages for all the RPs listed in the SsoSessions cookie—that is, WebSiteA and WebSiteB.

  5. The browser renders the first image, pointing to WebSiteA. Hence, it sends a GET for its source, which in fact delivers a cleanup message. WebSiteA already cleaned up its session because it was the originator of the Single Sign-out sequence. If the STS had known this, it could have avoided adding WebSiteA to the list of cleanup RPs; however, nothing bad happens, thanks to the idempotency requirements of wssignoutcleanup1.0 messages. WebSiteA simply returns the bits of the GIF indicating that cleanup successfully took place.

  6. The browser renders the image, pointing to WebSiteB. WebSiteB receives the cleanup message and reacts by deleting its own FedAuth cookie and returning the bits of the GIF of the check mark as expected. At this point, all the sessions have been cleaned up: the Single Sign-out concluded successfully, and the user can see on the STS page the list of Web sites he has been signed out from.

Once you get the hang of it, it’s really not that hard. One of the things I like best about this approach is that it allows you to herd the behavior of multiple Web sites without knowing any detail. Some sites could be hosted on your intranet, others could be hosted in the cloud, or sites could be running on different stacks and operating systems, but as long as they all speak via WS-Federation and share a common, trusted ground, the right thing just happens.

More About Sessions

I briefly touched on the topic of sessions at the end of Chapter 3, where I showed you how you can keep the size of the session cookie independent from the dimension of its originating token by saving a reference to session state stored on the server side. The WIF programming model goes well beyond that, granting you complete control over how sessions are handled. Here I’d like to explore with you two notable examples of that principle in action: sliding sessions and network load-balancer-friendly sessions.

Sliding Sessions

By default, WIF creates SessionSecurityTokens whose validity is based on the validity of the incoming token. You can overrule that behavior without writing any code, by adding to the <microsoft.identityModel> element in the web.config file something like the following:

<securityTokenHandlers>
    <add type="Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler,
               Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral,
               PublicKeyToken=31bf3856ad364e35">
      <sessionTokenRequirement lifetime="0:02" />
    </add>
</securityTokenHandlers>

Now, let’s say that you want to implement a more sophisticated behavior. For example, you want to keep the session alive indefinitely as long as the user is actively working with the pages. However, you want to terminate the session if you do not detect user activity in the past 2 minutes, regardless of the fact that the initial token would still be valid. This is a common requirement for Web sites that reveal personally identifiable information (PII) or give control to banking operations. Those are cases in which you want to ensure that the user is actually in front of the machine and the pages are not abandoned to the mercy (or mercenary instincts) of bystanders.

In Chapter 3, I hinted at this scenario, suggesting that it could be solved by subclassing the SessionAuthenticationModule. That is the right strategy if you expect to reuse this functionality over and over again across multiple applications, given that it neatly packages it in a class you can include in your code base. In fact, SharePoint 2010 offers sliding sessions and implements those precisely in that way. If, instead, this is an improvement you need to apply only occasionally, or you own just one application, you can obtain the same effect simply by handling the SessionSecurityTokenReceived event. Take a look at the following code:

<%@ Application Language="C#" %>
<%@ Import Namespace="Microsoft.IdentityModel.Web" %>
<%@ Import Namespace="Microsoft.IdentityModel.Tokens" %>
<script runat="server">
  void SessionAuthenticationModule_SessionSecurityTokenReceived
       (object sender, SessionSecurityTokenReceivedEventArgs e)
  {
    DateTime now = DateTime.UtcNow;
    DateTime validFrom = e.SessionToken.ValidFrom;
    DateTime validTo = e.SessionToken.ValidTo;
    double halfSpan = (validTo - validFrom).TotalMinutes / 2;
    if ( validFrom.AddMinutes( halfSpan ) < now && now < validTo )
    {
      SessionAuthenticationModule sam = sender as SessionAuthenticationModule;
      e.SessionToken = sam.CreateSessionSecurityToken(e.SessionToken.ClaimsPrincipal,
e.SessionToken.Context,
         now, now.AddMinutes(2), e.SessionToken.IsPersistent);
      e.ReissueCookie = true;
    }
  }
  //...

As you certainly guessed, this is a fragment of the global.asax file of the RP application. SessionSecurityTokenReceived gets called as soon as the session cookie is deserialized (or resolved from the cache if you are in session mode). Here you verify whether you are within the second half of the validity window of the session token. If you are, you extend the validity to another 2 minutes, starting now. That change takes place on the in-memory instance of the SessionSecurityToken. Setting ReissueToken to true instructs the SessionAuthenticationModule to persist the new settings in the cookie after the execution leaves SessionSecurityTokenReceived. Let’s say that the token is valid between 10:00 a.m. and 10:02 a.m. If the current time falls between 10:01 a.m. and 10:02 a.m.—say, 10:01:15—the code sets the new validity boundaries to go from 10:01:15 to 10:03:15 and saves those in the session cookie.

If the current time is outside the validity interval, this implementation of SessionSecurityTokenReceived will have no effect. The SessionAuthenticationModule will take care of handling the expired session right after. Note that an expired session does not elicit any explicit sign-out process. If you recall the discussion about SSO and SSOut just a few pages earlier, you’ll realize that if the STS session outlives the RP session the user will just silently re-obtain the authentication token and renew the session without even realizing anything happened.

Sessions and Network Load Balancers

By default, session cookies written by WIF are protected via DPAPI, taking advantage of the RP’s machine key. Such cookies are completely opaque to the client and anybody else who does not have access to that specific machine key.

This works well when all the requests in the context of a user session are aimed at the same machine. But what happens when the RP is hosted on multiple machines—for example, in a load-balanced environment? A session cookie might be created on one machine and sent to a different machine at the next postback. Unless the two machines share the same machine key and use it for encrypting the cookie instead of taking advantage of the DPAPI Encryption key, a cookie originated from machine A will be unreadable from machine B.

There are various solutions to the situation. One obvious one is using sticky sessions—that is, guaranteeing that a session beginning with machine A keeps referring to A for all subsequent requests. I am not a big fan of that solution because it dampens the advantages of using a load-balanced environment. Furthermore, you might not always have a say in the matter—for example, if you are hosting your applications on a third-party infrastructure (such as Windows Azure), your control of the environment will be limited.

Another solution is to synchronize the machine keys of every machine and use those for encrypting cookies. I like this better than using sticky sessions, but there is an approach I like even better. More often than not, your RP application will use Secure Sockets Layer (SSL), which means you need to make the certificate and corresponding private key available on every node. It makes perfect sense to use the same cryptographic material for securing the cookie in a load-balancer-friendly way.

WIF makes the process of applying the aforementioned strategy in ASP.NET applications trivial. The following code illustrates how it can be done:

public class Global : System.Web.HttpApplication
{
  //...
    void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs
e)
    {
      //
      // Use the <serviceCertificate> to protect the cookies that are
      // sent to the client.
      //
      List<CookieTransform> sessionTransforms =
        new List<CookieTransform>(new CookieTransform[] {
          new DeflateCookieTransform(),
          new RsaEncryptionCookieTransform(e.ServiceConfiguration.ServiceCertificate),
          new RsaSignatureCookieTransform(e.ServiceConfiguration.ServiceCertificate) });
      SessionSecurityTokenHandler sessionHandler = new
SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());

    e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
    }


    protected void Application_Start(object sender, EventArgs e)
    {
      FederatedAuthentication.ServiceConfigurationCreated += OnServiceConfigurationCreated;
    }

Instead of using the usual inline approach, this time I am showing you the code-behind file global.asax.cs. OnServiceConfigurationCreated is—Surprise! Surprise!—a handler for the ServiceConfigurationCreated event and fires just after WIF reads the configuration. If you make changes here, you have the guarantee that they will already be applied from the request coming in.

The code is self-explanatory. It creates a new list of CookieTransform transformations, which takes care of cookie compression, encryption, and signature. The last two take advantage of the RsaxxxxCookieTransform, taking in input the certificate defined for the RP in the web.config file.

The new transformations list is assigned to a new SessionSecurityTokenHandler instance, which is then used for overriding the existing session handler. From this point on, all session cookies will be handled using the new strategy. That’s it! As long as you remember to add an entry for the service certificate in the RP configuration, you’ve got network load balancing (NLB)–friendly sessions without having to resort to compromises such as sticky sessions.