Creating a Custom STS with Windows Identity Foundation


A while ago I was putting together a demo for Engineeringworld that would demonstrate how you could create your own SecurityTokenService (STS) using Windows Identity Foundation (WIF). I figured I could create an STS that would authenticate users against LinkedIn. To use the STS in my demo I needed a place to host the STS temporarily, so I thought it would be a good idea to host the STS in Windows Azure.
While I was putting together the demo I was reading the “Programming Windows Identity Foundation” book written by Vittorio Bertocci. As work on my demo progressed I read in Vittorio’s book that writing your own STS is probably not a good idea. An STS needs to be:
  • Secure – Relying Parties need to trust your STS. If it is not secure this will damage the trust you built up.
  • Available – Because an STS is a single entry point for possibly multiple applications, it must be available all the time. If your STS is down, none of the relying parties will be able to function correctly.
  • High-performing – Login experience is very important. While the number of users increases as more and more relying parties are added, performance must remain acceptable.
  • Manageable – You must be able to add or remove relying parties, monitor performance, manage claims, add or remove users, etc.
All these requirements imply that your STS needs to be thoroughly tested for security, functionality and performance. You need a trustworthy infrastructure to host your STS, which would cost a lot of money to provision and to maintain. Although the Windows Azure platform could take away some the worries such as performance and availability, it’s still probably not a good idea writing your own STS and you will probably be better of using a thoroughly tested STS like ADFS v2.
Ok, that being said, I had progressed way to far in my demo to stop now. Besides,…it was a nice challenge and it would give me a lot of inside in how the WS-Federation protocol works. Additionally I learned a lot about OAuth v1.0a as well, since LinkedIn uses this protocol to authenticate users. Mixing the two protocols turned out to be a great exercise in the beautiful world of Identity.
In this post I will show how to create a plain STS using the ASP.NET Security Token Service Web Site project template you get out-of-the-box when you install WIF. I will show what you when you create the STS and how WS-Federation applies to it. I will also show what the “Add STS Reference…” functionality does to an application that relies on WIF for authentication and authorization (a Relying Party or just RP).
In future posts I will show how to authenticate using the LinkedIn API an how you can convert your working project to host it in a Windows Azure WebRole. I will also show how you can use Windows Azure AppFabric Access Control Service as a Federation Provider (FP), which I’ll explain when I talk about Federated Identity. Finally I will explain why I didn’t deliver my prepared demo.
For all you folks who attended my talk at Engineeringworld, in this post I will also uncover why my improvised demo mysteriously failed.
Knipogende emoticon

ASP.NET Security Token Service Web Site

When you installed the Windows Identity Foundation SDK, you got some Visual Studio project templates for free, which make your live as developer so much easier. One of them is the ASP.NET Security Token Service Website template, which creates a website project with some pages, a configuration file with FormsAuthentication enabled and a metadata file used by WIF.
image
Figure 1: ASP.NET Security Token Service Web Site Template
Figure 1 shows the artifacts present in the ASP.NET Security Token Service Web Site template. The website is configured to use FormsAuthentication, thus every unauthenticated user will be redirected to the Login.aspx page where they will need to provide a username and password (or, as we’ll see in a future post, use some other form of authentication). The template does not implement the actual authentication of users for there is no user store behind it. The fact alone that the template is implemented in a Website project (as opposed to a Web Application project) indicates once again that it’s probably not good idea to use it for a production STS. It’s meant mainly to provide the developer an easy way to create a stub for testing a RP.
Next to the .aspx pages and the Web.config, you see a FederationMetadata.xml which is used by a wizard called FedUtil.exe to generate an STS reference. There are also some classes in the App_Code folder used for extending default logic and for creating an IClaimsIdentity. I will get into the details of these artifacts shortly, but first I want to look at the WS-Federation protocol and see how it’s implemented in the STS template.

WS-Federation

WS-Federation is an authentication protocol used to externalize authentication to provide better interoperability between applications and businesses. WS-Federation works in active scenario’s (rich clients) as well as passive scenario’s (stateless request/response). The scenario we’re interested in here is the passive scenario as we’re making more internet-based applications using the stateless HTTP protocol.
WS-Federation
Figure 2: The WS-Federation Protocol
Figure 2 depicts the communication process specified by the WS-Federation protocol. Let me write the process out in 9 steps:
  1. The user requests Page.aspx in my web application.
  2. In my web application’s configuration a couple of HttpModules are registered to handle incoming requests. The WSFederationAuthenticationModule processes the request and finds that the request does not contain a valid token. It responds to the browser with a HTTP/1.1 302 (FOUND) message with the address of the STS in the Location header, which is configured in my web application’s configuration file.
  3. My web application gets redirected to the Default.aspx page of the STS with some typical WS-Federation query string parameters (I won’t go in the details of these query string parameters, because then I would elaborate too much).
  4. Because the STS is configured to use FormsAuthentication and I’m not yet authenticated, the user gets redirected to the Login.aspx page as is configured in the Web.config of the STS. Here the user needs to, for instance, enter her username and password which will be validated by some self implemented mechanism (e.g. ASP.NET Membership Provider).
  5. When the user is authenticated she gets redirected back to the Default.aspx page which in turn processes the signin request by generating a SecurityToken with whatever claims you specified (I will go into this in a later post) and putting it into a signin response.
  6. Next, the browser gets redirected to the Page.aspx page of my web application. The WSFederationAuthenticationModule intercepts the request and now finds a (hopefully) valid token.
  7. The WSFederationAuthenticationModule passes the token on to the SessionAuthenticationModule which is also registered in the Web.config of my web application. The SessionAuthenticationModule sets a cookie named FedAuth in the response to the browser. For this it uses a HTTP/1.1 302 (FOUND) message with the address of the requested page in the Location header.
  8. The browser gets redirected back to the Page.aspx page with the FedAuth cookie in the header of the request. The SessionAuthenticationModule processes the cookie and create an IClaimsPrincipal with an IClaimsIdentity in it and let’s the request pass to it’s destination.
  9. Finally, my web application’s code is hit, returning the requested Page.aspx page.
The above steps are generally what the WS-Federation protocol describes. Of course the process is a lot more complicated under the hood, but with these 9 steps you get a good idea of what’s going on.

Signing In

protected void Page_PreRender( object sender, EventArgs e )
{
    ...
    try
    {
        if ( action == WSFederationConstants.Actions.SignIn )
        {
            // Process signin request.
            SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri( Request.Url );
            if ( User != null && User.Identity != null && User.Identity.IsAuthenticated )
            {
                SecurityTokenService sts = new CustomSecurityTokenService( CustomSecurityTokenServiceConfiguration.Current );
                SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest( requestMessage, User, sts );
                FederatedPassiveSecurityTokenServiceOperations.ProcessSignInResponse( responseMessage, Response );
            }
            else
            {
                throw new UnauthorizedAccessException();
            }
        }
        else if ( action == WSFederationConstants.Actions.SignOut )
        {
            ...
        }
        else
        {
            throw new InvalidOperationException();
        }
    }
    catch ( Exception exception )
    {
        throw new Exception( "An unexpected error occurred when processing the request. See inner exception for details.", exception );
    }
}
Codeblock 1: Process wasignin1.0
A signin request in WS-Federation always has a wa QueryString parameter with the value wasignin1.0. If the STS encounters this QueryString parameter a SigninRequestMessage is created from the url in the request. If the user is not authenticated at this point, the request will be redirected to the Login.aspx page as is configured in the Web.config. When the user is authenticated the request will be redirected back to the Default.aspx page. Because the QueryString parameters haven’t changed, the signin process will begin. Now the user is authenticated and so a new instance of CustomSecurityTokenService is created based on the CustomSecurityTokenServiceConfiguration. The CustomSecurityTokenService class contains some methods that enable the developer to modify the default behavior in regard to the validation of the relying party, which are out of scope for this post. The CustomSecurityTokenService also contains a method called GetOutputClaimsIdentity (Codeblock 2), which enables the developer to control what claims will be returned.
protected override IClaimsIdentity GetOutputClaimsIdentity( IClaimsPrincipal principal, RequestSecurityToken request, Scope scope )
{
	if ( null == principal )
	{
		throw new ArgumentNullException( "principal" );
	}

	ClaimsIdentity outputIdentity = new ClaimsIdentity();

	// Issue custom claims.
	// TODO: Change the claims below to issue custom claims required by your application.
	// Update the application's configuration file too to reflect new claims requirement.

	outputIdentity.Claims.Add( new Claim( System.IdentityModel.Claims.ClaimTypes.Name, principal.Identity.Name ) );
	outputIdentity.Claims.Add( new Claim( ClaimTypes.Role, "Manager" ) );

	return outputIdentity;
}
Codeblock 2: GetOutputClaimsIdentity
The ASP.NET Security Token Service Web Site template implements the GetOutputClaimsIdentity method so that it returns an IClaimsIdentity with two claims; Name and Role. In this method claims can be added or transformed as needed.

Metadata

Typically an STS contains a publicly accessible metadata file. This metadata file is a signed XML file that contains information about the owner of the STS, the offered claims, the endpoints on which a RP can reach the STS and much more that’s beyond the scope of this post. This metadata file (typically called FederationMetadata.xml) is signed, meaning that it cannot be changed in any way without invalidating the signature. The template generates the file as one long string of XML and even if you change the layout by hitting CTRL+A+K+F, the signature is invalidated and the metadata is useless to any RP wanting to use it.
There are tools however that let you enter your information and the claims you want your STS to offer and they will generate a valid signed metadata file, which you can include in your STS project. One of these tools is the WS-Federation Metadata Generation Wizard by ThinkTecture.
The WS-Federation metadata is used by tooling to generate the right configuration for RP’s. This way a lot of tedious and error prone work can be done by generators, making our life as a developer a lot easier once more.

Relying Party

Now that we have a basic STS in place, our RP needs to reference that STS and start using the WS-Federation protocol. When we installed the WIF SDK, we got an extra option in de context menu of a project in Visual Studio named “Add STS Reference…”. This option opens the FedUtil wizard that will guide use through a few simple steps to add a reference to our STS.
image
Figure 3: FedUtil Step 1: The Audience
In the first step we provide the location of the Web.config of our RP. When we opened FedUtil from within the context of a Visual Studio project, this value is already filled in for us. In the same step we need to provide the URL where the application will be listening on. This can be localhost for debugging. FedUtil only makes changes to the Web.config of our application, so any values entered here can be changed easily at deploy time. When our application is not hosted in IIS yet and we don’t use SSL, we get a warning when we want to continue, saying that we’re not using SSL. We can safely ignore this message for now. Of course in production we should always make sure we have valid certificates installed and that the application URI scheme is HTTPS.
image
Figure 4: FedUtil Step 2: The STS
Next we need to select the location of the WS-Federation metadata. This can be an URL to the metadata XML file that is publicly accessible on the STS, but during development we can also browse for the file on the file system.
image
Figure 5: FedUtil Step 3: Certificate Validation
The next step we need to indicate whether we want to validate the certificate chain or not. Validating the certificate chain ensures that the certificate is issued by a trusted Certificate Authority (CA). In our sample we won’t validate the certificate chain, because we will use self-signed certificates.
image
Figure 6: FedUtil Step 4: Token Encryption
The next step in the process is indicating whether we want our tokens to be encrypted or not. For sake of simplicity we won’t use token encryption here either.
image
Figure 7: FedUtil Step 5: Offered Claims
Next we get an overview of claims that the STS offers. These are the claims that are configured in the WS-Federation metadata of the STS. We cannot change the claims here.
image
Figure 8: FedUtil Step 6: Summary
Finally, we get an overview of all information we have entered and all we have to do now is click on ‘Finish’ and our RP will be configured to use WS-Federation and to externalize it’s authentication to our STS.
WRONG!!! This is exactly what went wrong with my improvised demo at Engineeringworld. Let’s look at what FedUtil has generated.
First of all FedUtil generated a FederationMetadata.xml file in my RP. I won’t go into this for now, but an STS can use this to validate the RP and to enable dynamic claims generation and stuff. Furthermore FedUtil changed the Web.config file.
<configSections>
  <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
Codeblock 3: configSection/section microsoft.identityModel
<location path="FederationMetadata">
	<system.web>
	  <authorization>
		<allow users="*" />
	  </authorization>
	</system.web>
</location>
Codeblock 4: Publicly Accessible FederationMetadata
FedUtil adds a new configSection named microsoft.identityModel and it opens up the FederationMetadata folder so that the WS-Federation metadata for the RP is publicly accessible (just as with the STS).
<!--Commented out by FedUtil-->
<!--<authorization><allow users="*" /></authorization>-->
<authentication mode="None" />
<compilation debug="true" targetFramework="4.0">
  <assemblies>
	<add assembly="Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
	<add assembly="System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
  </assemblies>
</compilation>
<!--Commented out by FedUtil-->
<!--<authentication mode="Forms"><forms loginUrl="~/Account/Login.aspx" timeout="2880" /></authentication>-->
Codeblock 5: Added Microsoft.IdentityModel Assembly and Changed Authentication Mode
FedUtil further adds the Microsoft.IdentityModel assembly and changes the authentication mode to ‘None’ in the system.web section of the Web.config.
<system.webServer>
	<modules runAllManagedModulesForAllRequests="true">
	  <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
	  <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
	</modules>
</system.webServer>
Codeblock 6: WSFederationAuthenticationModule and SessionAuthenticationModule
The WSFederationAuthenticationModule and SessionAuthenticationModule HttpModules are added to the system.webServer section.
<microsoft.identityModel>
	<service>
	  <audienceUris>
		<add value="http://localhost:777/" />
	  </audienceUris>
	  <securityTokenHandlers>
		<securityTokenHandlerConfiguration saveBootstrapTokens="true" />
	  </securityTokenHandlers>
	  <applicationService>
		<claimTypeRequired>
		  <!--          Following are the claims offered by STS 'http://www.linkedinsts.nl/'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
		  <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="false" />
		  <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/dateOfBirth" optional="true" />
		  <claimType type="http://www.linkedinsts.nl/claims/2011/01/headline" optional="true" />
		  <claimType type="http://www.linkedinsts.nl/claims/2011/01/industry" optional="true" />
		  <claimType type="http://www.linkedinsts.nl/claims/2011/01/location" optional="true" />
		  <claimType type="http://www.linkedinsts.nl/claims/2011/01/memberGroup" optional="true" />
		  <!--Following are the claims offered by STS 'https://engineeringworld.accesscontrol.appfabriclabs.com/'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
		  <!--          <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />-->
		  <!--          <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />-->
		  <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />
		  <claimType type="http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider" optional="true" />
		  <!--Following are the claims offered by STS 'https://engineeringworld-demo.cloudapp.net'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.-->
		  <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateOfBirth" optional="true" />-->
		  <!--<claimType type="http://engineeringworld-demo.cloudapp.net/identity/2011/02/claims/headline" optional="true" />-->
		  <!--<claimType type="http://engineeringworld-demo.cloudapp.net/identity/2011/02/claims/industry" optional="true" />-->
		  <!--<claimType type="http://engineeringworld-demo.cloudapp.net/identity/2011/02/claims/location" optional="true" />-->
		  <!--<claimType type="http://engineeringworld-demo.cloudapp.net/identity/2011/02/claims/memberGroup" optional="true" />-->
		  <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
		  <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
		</claimTypeRequired>
	  </applicationService>
	  <certificateValidation certificateValidationMode="None" />
	  <federatedAuthentication>
		<wsFederation passiveRedirectEnabled="true" issuer="https://engineeringworld-demo.cloudapp.net/" realm="http://localhost:777/" requireHttps="false" />
		<cookieHandler requireSsl="false" />
	  </federatedAuthentication>
	  <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
		<trustedIssuers>
		  <add thumbprint="39AC3758F0EBFABE20F0E7EB889430549983D1A9" name="http://www.linkedinsts.nl/" />
		  <add thumbprint="6230CDA6652AD17F887E7EF36D5149C8B38D64B1" name="https://.../" />
		  <add thumbprint="824413764DC4829DB867087DC15B59BE5028B484" name="https://..." />
		</trustedIssuers>
	  </issuerNameRegistry>
	</service>
</microsoft.identityModel>
Codeblock 7: The microsoft.identityModel Configuration Section
The microsoft.identityModel configurationsection contains all the settings we entered In the FedUtil and the claimtypes that are offered by the configured STS.
Now here it comes. While these changes, made by FedUtil, work perfectly in IIS 7 using integrated pipeline mode, they don’t work in the development webserver. We need to do two things:
  1. Add the httpRuntime element to the system.web section with the requestValidationMode set to ‘2.0’
  2. Add the WIF modules to the system.web section as well
<httpRuntime requestValidationMode="2.0" />
<httpModules>
  <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</httpModules>
Share

,

Comments are closed.