Koen about .Net

August 2, 2010

Making a web application use an active STS

Filed under: Development, dotnetmag — Tags: , , , — koenwillemse @ 19:46

At my current assignment we’re working on a solution which among others consists of a custom Security Token Service which is used for the authentication of users in a web portal. In our case we’ve created an active STS by using Windows Identity Foundation and WCF. We’re now working a demo web application which uses the STS (and some other functionality we’re building) to show the team which creates the portal how they should consume the STS.

Since there is almost NO decent documentation about WIF, I started googling. The problem which I ran into was that the information I found was almost all about scenario’s with a passive STS. I found a bit information about consuming an active STS but all not complete, so I’ll put all the steps I had to take right here, so it can help some others.

First of all, I eventually found this blog post, which made it almost work for me. There was just some information missing which caused me to search for another 1 – 2 hours.

These are the steps to take when you want to consume an active STS from a web application:

  1. Add a reference to the Microsoft.IdentityModel assembly (WIF).
  2. Add the definition of the microsoft.IdentityModel config section to your config like this (check the correct version of the dll of course):
    <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    
  3. Add the following two HttpModules to your config (when using IIS7, add them to you system.webserver section, otherwise to you system.web section):
    <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" />
  4. The authentication mode should be set to None:
    <authentication mode="None" />
  5. Add the configuration for the microsoft.IdentityModel section, for instance:
    <microsoft.identityModel>
    <service>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
    <trustedIssuers>
    <add thumbprint="{Add the thumbprint of the certificate used by your STS, for instance: 80481e4041bd6758400c62e2c811831b98eed561}" name="{Add the name of the certificate, for instance: CN=devsts}" />
    </trustedIssuers>
    </issuerNameRegistry>
    <audienceUris>
    <add value="{Add the applies to url of your web application}"/>
    </audienceUris>
    <federatedAuthentication>
    <wsFederation passiveRedirectEnabled="false" issuer="{The address of the STS, for instance: https://devsts/mySts.svc}" realm="{The applies to address of your web application, for instance: http://myrelyingparty.nl}" persistentCookiesOnPassiveRedirects="true" />
    <cookieHandler requireSsl="false" />
    </federatedAuthentication>
    <serviceCertificate>
    <certificateReference x509FindType="FindByThumbprint" findValue="{The certificate used by your STS, for instance: 80481e4041bd6758400c62e2c811831b98eed561}" storeLocation="LocalMachine" storeName="My"/>
    </serviceCertificate>
    </service>
    </microsoft.identityModel> 

    As you can see, you register your information about the certificate being used by your sts and the information about your application, the relying party.
    The line about the cookieHandler was the one that caused me some problems because I didn’t have that. The problem is that my local site was working on http and not https, but the created cookies required https. I didn’t have this line at first, which had the effect that the session cookie was not maintained over postbacks.

  6. After you’ve configured everything, you can use the following code to consume your STS and get an IClaimsIdentity:
    // authenticate with WS-Trust endpoint
    var factory = new WSTrustChannelFactory(
    new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
    new EndpointAddress("<a href="https://devsts/MySts.svc">https://devsts/MySts.svc</a>"));
    factory.Credentials.UserName.UserName = usernameField.Text;
    factory.Credentials.UserName.Password = passwordField.Text;
    var channel = factory.CreateChannel();
    var rst = new RequestSecurityToken
    {
    RequestType = RequestTypes.Issue,
    AppliesTo = new EndpointAddress("http://myrelyingparty.nl/"),
    KeyType = KeyTypes.Bearer
    };
    var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
    // Now you parse and validate the token which results in a claimsidentity
    var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
    var token = handlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml)));
    var identity = handlers.ValidateToken(token).First();
    
    // Create the session token using WIF and write the session token to a cookie
    var sessionToken = new SessionSecurityToken(ClaimsPrincipal.CreateFromIdentity(identity));
    FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
    
    //Perform some redirect
    Response.Redirect("~/secure/default.aspx");
    

In our situation, this was not complete, since we need the SAML token received from the STS furtheron to authenticate to WCF services which we consume. After using reflector and trying something out, it can be easily done by changing just two lines of code. I can be done by the following:

var identity = handlers.ValidateToken(token).First();
Thread.CurrentPrincipal = new ClaimsPrincipal(new IClaimsIdentity [] { new ClaimsIdentity(identity.Claims, token) });

The code itself looks probably a bit strange, since we get a ClaimsIdentity from the ValidateToken and then we create another ClaimsIdentity. I hoped that WIF would have used this way to construct the identity, of at least provide an overload or something to do this. I created the identity like this, since the securitytoken is now availalbe in the BootstrapToken property of the ClaimsIdentity. At first we were thinking that we had to keep the security token in session but that is not necessary when you do it like this. Now we can access it simply with the following lines of code:

var identity = Thread.CurrentPrincipal.Identity as ClaimsIdentity;
var theOriginalSecurityToken = identity.BootstrapToken;

I hope this helps somebody else. It would have saved me a lot of time if I could have found this information somewhere.

Update (17-8-2010)

We’ve been using the code as listed above, but we ran into some problems, because the retrieved token is a GenericXmlSecurityToken which caused problems when supplying it to our backend services.

After some searching I found that it is possible to get the BootstrapToken property filled by WIF, but you need to set a configuration switch (some decent documentation would really be helpfull). All you have to do is change the following in your web application configuration (add the saveBootstrapTokens attribute):

<microsoft.identityModel>
<service saveBootstrapTokens="true>
<issuerNameRegistry ……

The code to creating your principal is then the following:

var identity = handlers.ValidateToken(token).First();
Thread.CurrentPrincipal = new ClaimsPrincipal(new [] { identity });

Now the BootstrapperToken is a SamlSecurityToken which is exactly what we want to be able to authenticate to the backend services. I’ll show in a new post how we have all this tied together.

Blog at WordPress.com.

%d bloggers like this: