Single Sign-On

A Developer's Introduction To Active Directory Federation Services

Keith Brown

This article discusses:
  • What federation means
  • Implementing federation in ASP.NET applications using ADFS
  • Trust relationships and security considerations
This article uses the following technologies:
ADFS, ASP.NET

Contents

A Solution Using ADFS
ADFS Architecture
Federated Logon Walkthrough
Securing the Federated Logon
Where Do You Come From, User?
Claims and Transformation
Where Do Claims Come From?
Anonymity
The Federation Service Proxy
ADFS-Enabling Your Web Application
Getting Started with ADFS in the Lab

One of the most important components of Windows Server® 2003 R2 is Active Directory Federation Services (ADFS). ADFS solves a number of problems—one of the most obvious and compelling being business-to-business automation. In this article I'm going to take a look at ADFS from the perspective of a developer who is building a Web application and wants to allow other organizations to use it (for an administrator's viewpoint, see Matt Steele's article in TechNet Magazine.

What kind of business-to-business problems am I referring to? Imagine that a bicycle manufacturer called Fabrikam wants to expose a Web application that will allow authorized dealers to purchase bikes and parts at wholesale prices. There are over two hundred dealers, each with several people who need to use the application. Fabrikam is going to need a secure logon mechanism.

An obvious solution would be to create a database containing user names and passwords, but this could become very costly to manage. If someone makes a call to Fabrikam claiming to be an employee of a dealer, how is Fabrikam going to verify this claim? They'll probably want to contact someone they trust at the dealership to verify the employee's status before provisioning a new account. Just consider the maintenance cost of such a user account: people forget user names, passwords, and have other problems. And what happens when the employee is terminated from the dealership? Is anyone going to remember to notify Fabrikam that a user account should be removed (or deprovisioned, in identity lingo)? If not, that user could go home and place false orders on the dealer's behalf.

Passwords themselves pose another problem. As computing power has increased, passwords have become easier and easier to attack, and many organizations now prefer to use stronger authentication techniques like smart cards. But because Fabrikam must work with so many different dealerships, it's going to have a difficult time supporting anything stronger than passwords.

Notice that trust is a factor here as well. Fabrikam trusts each dealership to supply an accurate list of employees who should be allowed to make purchases using Fabrikam's Web application.

A Solution Using ADFS

ADFS helps you establish trust relationships and reduces the need for provisioning user accounts. Why should you bother building a user account database for your application when your dealerships already have software that authenticates their users?

ADFS is the Microsoft implementation of the WS-Federation passive requestor profile protocol (passive indicates that all the client needs is a cookie- and JavaScript-capable Web browser—a passive agent that does not run any special code to help implement the protocol). Here's how this protocol works: when a user at a dealership points her browser at Fabrikam's purchasing application, her browser will eventually be redirected back to a Web site at the dealership where she works, so that the dealership can authenticate her. Then her browser will be redirected back to Fabrikam, and this new request will carry a signed statement by the dealership indicating that this is an actual employee who is authorized by the dealership to use the application. (I'm simplifying this quite a bit.) The point is that Fabrikam trusts the dealership to authenticate its own users, which is key to federation technology.

If a dealership has software that supports the WS-Federation passive profile, Fabrikam doesn't need to provision any user accounts for employees at that dealership. It doesn't need to worry about password reset requests. When a user at the dealership switches roles and no longer works in the purchasing department, as long as her identity information is changed to reflect this, when she tries to use Fabrikam's purchasing application the statement from the dealership will indicate that she's no longer a purchasing agent and she'll be denied access. Or if she quits her job at the dealership and her account is deprovisioned, she'll also no longer be able to use Fabrikam's purchasing application. By federating with the dealership, Fabrikam is virtually guaranteed to receive more relevant, up-to-date information about the user's identity.

ADFS is built on standards like WS-Federation, which was coauthored by Microsoft, IBM, Verisign, BEA, and RSA Security. Different organizations often run very different software. If Fabrikam uses Windows Server 2003 R2 with ADFS, but has dealerships running IBM WebSphere or BEA WebLogic, this really shouldn't be a problem because WebSphere and WebLogic both implement WS-Federation.

End users also get a better deal with federated identity. Instead of having to remember yet another password, a purchasing agent at a dealership can simply point her browser at Fabrikam's application and immediately start working. If the dealership's authentication system supports an integrated logon through a browser, as Windows does with Internet Explorer®, the user won't even be prompted for her credentials; she'll be authenticated silently and the federation service will translate the local knowledge of her identity into the signed statement for Fabrikam that I mentioned earlier. This is the brass ring of Web security: single sign-on across the Internet and across organizational and technological boundaries.

ADFS Architecture

Now I'm going to show you the various parts of ADFS and explain some terminology. In any given federation relationship, one side supplies the users (accounts) and the other side supplies the applications (resources). When you install ADFS, you'll configure its trust policy using the ADFS administration snap-in, which shows up in your Administrative Tools folder, to indicate the list of partners with which you want to federate. Users from the account partner will be accessing ADFS-enabled applications in the resource partner. Figure 1 shows the tree view of ADFS trust policies from either side of a relationship, as seen from the ADFS administration snap-in.

Figure 1 Policy for Two Partners

Figure 1** Policy for Two Partners **(Click the image for a larger view)

On either side sits what is known as a federation service. Each federation service exposes a Web application, and it's expected that the user's browser will be redirected to these applications in order to establish a logon. The federation service resolves the impedance mismatch between the account and resource partners, so much so that the partners don't have to be using the same identity or operating system technology. Your app doesn't need to worry much about the federation service; the ADFS team provides a Web agent that runs in the ASP.NET pipeline as an HttpModule that will do the heavy lifting for you. All you need to do is configure your application to use the Web agent and supply some configuration settings so it knows where to find your company's federation service. Figure 2 shows the basic structure of ADFS and how the user's browser interacts with the various components. This shows what things would look like if ADFS were on both sides of the wire, but it's easy enough to imagine the account partner, for example, being implemented in WebSphere with an IBM directory service behind it, exposing a Java-language federation service.

Figure 2 ADFS Architecture

Figure 2** ADFS Architecture **

Federated Logon Walkthrough

When Alice, an authorized user from one of Fabrikam's dealerships, points her browser at Fabrikam's purchasing application for the first time, the Web agent notices that she doesn't have an ADFS cookie. She's not logged in. So the Web agent redirects her browser to Fabrikam's federation service before the purchasing application even sees the request. Fabrikam's federation service then redirects the browser to the dealership's federation service, adding to the request a unique identifier for Fabrikam's federation service so the dealership will know which partner is requesting a logon. When Alice's browser ends up back at her own federation service, she's asked to authenticate. Because her dealership is using integrated Windows authentication, Alice's browser automatically responds, establishing a logon with her federation service. Once that logon is successful, her federation service issues a Security Assertion Markup Language (SAML) token describing Alice, and redirects Alice's browser back to Fabrikam's federation service, sending the SAML token along with the request as part of the POST body (the page that is returned has JavaScript that auto-submits the form containing hidden input elements). Fabrikam reads the contents of the token, and issues a different SAML token that contains the set of claims that the application will ultimately see (I'll explain why two separate SAML tokens are used in the section on claims transformation). This second token is sent using the POST method and written out to a cookie from fabrikam.com, allowing Alice to use the purchasing application until her cookie expires, which by default is in 10 hours.

Remember the Web agent in the purchasing application that was looking for a logon cookie? That's what started this whole sequence of events. Now that the cookie exists, the Web agent peels it apart and reads the claims in the SAML token that was issued by Fabrikam's federation service. The Web agent allows the Web page in the purchasing application to execute. If the application needs to know the logged-on user's name, it needs only to look in the usual place: HttpContext.User.Identity.Name.

The implementation of IIdentity used to convey this information is of type SingleSignOnIdentity, which also exposes a number of other useful properties, including the authentication method used by the account partner to authenticate the user in her home realm. From this class the application can also discover the entire set of claims sent by its federation service.

You should note that the partner federation services never directly talk to one another. It's only through browser redirections and associated query strings and POST bodies that the services are able to communicate.

Securing the Federated Logon

Now let's dig into some of the details to show you how the logon is secured. One attack vector would be to try to read the SAML tokens off the wire and then replay them at your convenience so you could impersonate a legitimate user. To prevent this, all of the communication occurs over HTTPS. This is critical; if you try to install ADFS without already having an SSL certificate installed for the default Web site in IIS, the ADFS installer will warn you and quit before it even gets started.

What about the logon cookies that contain claims? If the user wanted to elevate her privileges to an application she could simply add claims to the SAML token in her cookie! But this type of tampering can be detected because each SAML token is signed with a private key known only by the issuing federation service. This has implications for deploying ADFS. If you're acting as a resource partner, each of your account partners must supply certificates for their account federation services. Your resource federation service will use the account partner's certificate to verify signatures on the SAML tokens that it issues. The Web agent must do something similar, verifying that each SAML token it receives via a logon cookie was signed by the resource federation service.

Cookies issued by ADFS are always marked with the Secure bit, which indicates that the browser should only send the cookie over HTTPS, not HTTP. So if any part of your application runs over HTTP, the logon cookie won't be present and you won't have access to the user's identity information. It'll feel as though the user is anonymous. I'd recommend running the entire application over SSL to simplify your life and reduce the chances of leaking sensitive data to an eavesdropper.

Cross-site scripting (XSS) bugs in an ADFS-enabled Web application are devastating, as they would be in any system that uses cookies to represent logon sessions. Cookies can easily be stolen from an application that has this type of vulnerability, and if you steal the cookie, you've stolen the logon. As a defense-in-depth measure, ADFS logon cookies time out after a work day by default, and you can adjust these timeout values in the ADFS trust policy. If you're not entirely comfortable with what XSS is and how to prevent it, take a half an hour to work through the interactive XSS lab I wrote.

Where Do You Come From, User?

Fabrikam has many dealerships that act as federated account partners. When one uses the purchasing application, how does Fabrikam's federation server know to which partner to redirect the user's browser? It doesn't. With multiple partners, Fabrikam's federation server must pause the logon process to display a list of partners to the user so she can select one to authenticate her. This is known as home realm discovery. If the client lies about her home realm, she's only going to inconvenience herself, as she won't have credentials to authenticate with any of the other dealerships.

One of the goals of building a great ADFS application is to avoid bothering the user with these sorts of details. The Northwind bike shop can eliminate this step by having its users access Fabrikam's Web site through a link with a magical query string parameter, whr. (I like to think of this as "which home realm?") The Web agent looks for this query string argument, and if it finds it, strips it out of the request during preprocessing. The value of this parameter is the URI of the partner federation server (each federation server names itself with a URI). So employees at Northwind shop could be presented with a link that looked something like this:

https://fabrikam.com/purchasing.aspx/?whr=urn:federation:
Northwindbikeshop

This gives ADFS enough of a hint to avoid having to display an interactive home realm discovery page.

Claims and Transformation

I've been talking a lot about claims so far in this article. A new, increasingly important security concept on the Windows platform, claims are a general way of making statements about an entity such as a user. Consider a Kerberos ticket, which contains user and domain group identifiers for the logged-on user. This is really just a set of claims signed by the domain controller that issued the ticket. The user identifier is an identity claim. But while this may be useful for auditing or personalization, it's not typically used for access control. The groups in the ticket are more likely to be used for performing access checks. For example, if you're a member of the Managers group, you may be allowed to approve high-value purchases.

ADFS supports three types of claims: identity claims, group claims, and custom claims. An identity claim takes the form of a user principal name (UPN), an e-mail address, or an arbitrary string called a common name. A group claim is Boolean (either a member or not a member), and it doesn't necessarily have to represent a Windows group: it might simply represent a logical role that your application understands. A custom claim contains a string value. Age, gender, or perhaps even ManagerName would be examples of custom claims that you can configure in ADFS. These types of custom claims should make you start thinking about privacy issues.

Claim transformation is how ADFS addresses both privacy issues and other practical concerns such as the fact that not every company uses the same terminology, and that not every resource partner needs to know a user's e-mail address or age. This is why ADFS trust policy allows you to configure each partner differently; you should only send the claims that partner needs.

Each company defines a set of organizational claims; this is like determining the vocabulary that it understands. For example, Fabrikam might define a group claim called "Owner" that it uses to represent the owner of a bike dealership. But the Northwind bike shop may represent this role with a claim called "Manager." That's OK as long as the meaning is the same, because the owner of Northwind can use his resource federation trust policy to map his outgoing "Manager" claim into an "Owner" claim that Fabrikam will understand. Administrators can set up these sorts of mappings on either side of a federation relationship. Figure 3 shows an example of a claim mapping in a resource partner's trust policy.

Figure 3 Claim Mapping

Figure 3** Claim Mapping **(Click the image for a larger view)

Some claim mappings are too dynamic to be represented by a static mapping in trust policy. Suppose a resource partner needs a claim called IsOfLegalVotingAge to certify that someone is 18 years or older, but all you have is the user's date of birth as a custom claim from Active Directory®. What you need is a claims transformation module, which is an assembly with a class that implements a managed class called IClaimTransform. You can wire this up to your trust policy via the trust policy property sheet shown in Figure 4. This module is called twice: once before the normal trust policy mapping occurs and once afterward, so you can perform pre- or post-processing. Every ADFS trust policy allows a module like this to be installed, which means you can do dynamic claims transformation on both the account and resource side of a federation.

Figure 4 Claims Transformation Module

Figure 4** Claims Transformation Module **

Where Do Claims Come From?

Unless you're writing a claims transformation module that generates claims dynamically, all of your claims will originate from an account store, which can be either Active Directory or Active Directory Application Mode (ADAM), a lightweight directory server based on the Active Directory code base. For Active Directory account stores, you can map group claims onto one or more groups in Active Directory; with an ADAM store, you must supply the name of an attribute on the user object and a value to look for to indicate whether the group claim should be sent for a given user. Each custom/identity claim is mapped directly onto a single attribute of the user object in the directory.

The beauty is that an administrator can take advantage of existing identity data in the company directory to integrate with partners and, in many cases, there's no need to write any code.

Anonymity

One thing that's really interesting about claims-based systems is that there's often no need to send the user's name at all. Perhaps all the partner cares about is whether or not the user is authorized to perform the request she's making. But it's often convenient for the partner to at least have some identifying handle from which he can hang personalization data or other state for the user.

To support this anonymous mode of operation, there's a special built-in transformation for identity claims called enhanced identity privacy. If you're an account partner and would like to ensure that your users remain anonymous with a particular resource partner, just enable this feature for that partner in your trust policy, and instead of seeing

alice@northwindbikes.com

the partner will instead see something like this:

tQZPfFuodGysa4t40oj+kM2vBIU=@northwindbikes.com

To generate this handle, the federation service hashes a combination of the actual identity claim, the resource partner's URI, and a salt value that only your federation service knows (this is called the "privacy key" in ADFS terminology). Including the partner's URI ensures that handles differ for each resource partner, preventing them from colluding to build a dossier of data on a user. The privacy key is included to make dictionary attacks difficult.

Despite protecting the identity from the resource partner, this feature still maintains traceability. In the event that an identity needs to be determined, the hashed identity can be found in the resource logs and provided to the account partner administrator, who can use the account event logs to determine which user received that identity.

The Federation Service Proxy

When you install the ADFS federation service, you'll see a new Web application in IIS manager called "adfs." Underneath this, you'll find subdirectories called "fs" and "ls," which stand for Federation Service and Logon Service. The Logon Service is really just the browser-based front end of ADFS; if you poke around under the ls directory you'll see a set of ASPX pages (by the way, you can customize these pages if you like, so it's worthwhile to explore them). This is where the browser is redirected during a federated logon. I've not found the term "Logon Service" anywhere in the published documentation for ADFS, but it's apparent by looking at the code that this is what it's called internally. Under the fs directory you'll find a file called FederationServerService.asmx, the Web service which is the back end of ADFS, technically called a Security Token Service (STS).

The reason these are split up is so that the front end (the Logon Service) can be hosted in a perimeter network (often referred to as a DMZ) and can communicate with the back-end of the federation service via the ASMX Web service, which lives on the internal network. In this configuration the front-end is known as the federation service proxy.

Whether or not your administrator splits up the federation service by using the proxy on a separate machine has little impact on how you write your application, but it's good to know that ADFS was designed to be DMZ-friendly.

ADFS-Enabling Your Web Application

If you've been tasked with building a Web application that supports ADFS logons, you'll need to do a few things. You'll need to know how to set up your web.config file to load and configure the ADFS Web agent. Figure 5 shows the web.config file I used for the sample application for this article.

Figure 5 Sample Web.config

<configuration>

  <configSections>
    <sectionGroup name="system.web">
      <section name="websso" type=

        "System.Web.Security.SingleSignOn.WebSsoConfigurationHandler, 
         System.Web.Security.SingleSignOn, Version=1.0.0.0, 
         Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null"/>
    </sectionGroup>
  </configSections>

  <system.web>

    <!-- we’re not using any of the standard ASP.NET auth techniques, 
         we’re using ADFS! -->
    <authentication mode="None" />
    <customErrors mode="Off"/>
    <sessionState mode="Off" />

    <!-- pull in ADFS assemblies -->
    <compilation debug=‘true’ defaultLanguage=‘c#’>
      <assemblies>
        <add assembly="System.Web.Security.SingleSignOn, Version=1.0.0.0, 
                       Culture=neutral, PublicKeyToken=31bf3856ad364e35, 
                       Custom=null"/>
        <add assembly="System.Web.Security.SingleSignOn.ClaimTransforms, 
                       Version=1.0.0.0, Culture=neutral, 
                       PublicKeyToken=31bf3856ad364e35, Custom=null"/>
      </assemblies>
    </compilation>

    <!-- pull in the module containing the ADFS web agent -->
    <httpModules>
      <add name="ADFS Web Agent" type=
          "System.Web.Security.SingleSignOn.WebSsoAuthenticationModule,
           System.Web.Security.SingleSignOn, Version=1.0.0.0, 
           Culture=neutral, PublicKeyToken=31bf3856ad364e35, 
           Custom=null" />
    </httpModules>

    <!-- the web agent looks here for its configuration -->
    <websso>
      <authenticationrequired />
      <eventloglevel>55</eventloglevel>
      <auditsuccess>2</auditsuccess>
      <urls>
        <returnurl>https://resource.local/web/</returnurl>
      </urls>
      <cookies writecookies="true">
        <path>/web</path>
        <lifetime>240</lifetime>
      </cookies>
      <fs>https://resource.local/adfs/fs/federationserverservice.asmx</fs>
    </websso>
  </system.web>

  <system.diagnostics>
    <switches>
      <!-- enables full debug logging -->
      <add name="WebSsoDebugLevel" value="255" /> 
    </switches>
    <trace autoflush="true" indentsize="3">
      <listeners>
        <!-- either create a c:\logs directory and grant Network Service 
             permission to write to it, or remove this listener -->
        <add name="MyListener" type=
            "System.Web.Security.SingleSignOn.
                 BoundedSizeLogFileTraceListener, 
             System.Web.Security.SingleSignOn, Version=1.0.0.0, 
             Culture=neutral, PublicKeyToken=31bf3856ad364e35, 
             Custom=null"
             initializeData="c:\logs\webagent.log" />
      </listeners>
    </trace>
  </system.diagnostics> 
</configuration>

Once your web.config file is set up, you should be able to accept logins from ADFS account partners. The next step is to start making security decisions based on claims.

There are actually two different ADFS Web agents. One is called the Web Agent for Windows NT® Token-based Applications. This Web agent will generate a real Windows logon session for the remote user. It generates a login from the user principal name via the S4U Kerberos extensions I described in my April 2003 Security Briefs column (a custom authentication package is used if not running in Windows Server 2003 native mode). The benefit is that you can impersonate this logon and pass off authorization duties to existing secure resource managers behind you, such as the file system or SQL Server™. (If these resources are remote, you'll also need to enable protocol transition in Active Directory.) One of the biggest drawbacks is that you're going to have to provision a user account in your domain for each incoming user, which destroys a lot of the benefit of using ADFS in the first place! (However, users will not need to know their resource account password, and most likely they won't even know there is another account provisioned for them.)

If you want to buy into federation, you'll need to build your app to be claims aware, and you won't be able to rely on impersonation because you won't have a Windows domain account representing the users from your account partners. You'll use the Web Agent for Claims-Aware Applications, which doesn't attempt any mapping onto Windows accounts, and hands you the incoming claims via HttpContext.User. This is the approach I took in my sample.

Writing code to check claims is not at all difficult. In fact, if you primarily rely on group claims, you need not think much about claims at all. Just continue to use HttpContext.User.IsInRole to check for the presence or absence of group claims, treating them as application roles. This means you can also use controls like the ASP.NET LoginView, which bases its output on roles discovered via the HttpContext.User property.

If you want to read the value of custom claims, you'll need to write a bit of new code. This sample looks for a claim called Title:

string GetTitle() 
{
    SingleSignOnIdentity id = (SingleSignOnIdentity)User.Identity;
    SecurityPropertyCollection c =
        id.SecurityPropertyCollection.GetCustomProperties("Title");
    return (1 == c.Count) ? c[0].Value : string.Empty;
}

One interesting piece of data that's passed along is the original method used to authenticate the client in her home realm. ADFS actually supports four authentication techniques: Windows integrated, SSL client certificate, Basic authentication, and ASP.NET Forms authentication. You can discover which was used via the AuthenticationMethod property on the SingleSignOnIdentity object. Don't confuse this with the original AuthenticationType property on IIdentity; you'll find that it always returns WebSSO, which basically means ADFS was responsible for the login.

Other interesting properties include AuthenticatingAuthority, the URI of the account partner that authenticated the client. And it's always a good idea to provide a Log Off button, so you'll find the SignOutUrl property useful in this regard.

The sample application for this article dumps out all of the public properties of SingleSignOnIdentity into a table, as shown in Figure 6. It also dumps out the collection of security properties, which basically shows all of the claims that were sent in their raw form. Before you can run this application, though, you'll need to set up ADFS.

Figure 6 Sample Application

Figure 6** Sample Application **(Click the image for a larger view)

Getting Started with ADFS in the Lab

If you'd like to experiment with ADFS, you'll need to make time to set up an appropriate environment. I took notes as I built my claims-based B2B setup and came up with a reasonably quick way to put together a set of three Virtual PC images that represent an account partner with a single client machine along with a resource partner. I've put these notes up on my wiki. Some notes may seem brief, but they'll make sense if you've worked with Virtual PC and Windows. I walk you through setting up domains and using SYSPREP, which may be new to you. You can edit the wiki by double-clicking the page, so feel free to add comments for fellow readers; just try to be brief in the spirit of my original notes.

There is a sample application up on the wiki as well, including the config file you can use to test with. Just copy the default.aspx and web.config files into a virtual directory on the resource partner image; then you should be able to log into the client machine and point your browser at the Web application in the partner application to log into it.

Keith Brown is a cofounder of Pluralsight, a premier Microsoft .NET training provider. Keith is the author of Pluralsight's Applied .NET Security course, as well as several books, including the .NET Developer's Guide to Windows Security, available in print and on the Web. Learn more at www.pluralsight.com/keith.