SSO with Windows Identity Foundation: Part IV - STS

Welcome back to keep on building on our understanding of an STS. This part is fully about creating a response message to the message we previously sent from the relying party.

Last time we were able to make our forms authentication application automatically redirect to our STS, and we were able to successfully receive and log the contents of the message that we were getting from our RP.

This time we'll focus on answering that request from the STS. This part requires us to set up a certificate on our computer, and it requires some boilerplate code which will read the certificate for us.

Self signed certificate

You can get all the code used in this part from here.

Let's start with installing the ceritificate. To get started go grab Pluralsight's self signed certificate tool here. With a few clicks you can create a certificate in your local certificate store that we'll need to sign the eventual response we'll be sending back to the relying party.

So just open up the MakeCert.exe and create with the default settings a self signed certificate into your Local Machine/My store. Make sure to grab the certificate's thumbprint from the following window:

To view this certificate - open up MMC.exe, and add a Certificates snap-in for your Computer account, then browse into Personal -> Certificates, and you should find a freshly generated certificate by the name of "localhost".

Let's implement the STS.

So previously we only had some debug code in our AuthenticationController's Index action. Let's scrap what we previously had and write some code we can actually use.

Our plan is to create a fake user as if this user was built against information we got from some external user store. Then we want to create an authentication token for said user, and return the token back to the relying party in a ws federation message.

Let's begin by creating said user:

Then we'll implement our own version of a security token service by deriving from the class SecurityTokenService:

In the above class we have an absolute ton of things we could override, but the above two things are the only two we are required to override, and as such we'll try to keep it simple.

SecurityTokenService.GetScope

GetScope simply must build a scope object, and within this scope object we are required to provide some details about the relying party we're authenticating against. These include things such as which URL the RP resides in and what URL we should redirect back to after we're done authenticating. An important step to understand is that, if all the URLs here came originally from the WS federation message, you should in a production environment validate those URLs correctly that they belong to a list of relying parties, and that they have absolutely nothing dubious in them.

We are also required to provide a set of credentials with which the security token will be signed upon its creation. Whenever a token is created its content is also signed, so basically a hash of the content is included, with a certificate to prevent its content from being tampered mid flight. Hence we created the self signed certificate earlier.

SecurityTokenService.GetOutputClaimsIdentity

The idea behind a security token service is that you get multiple input identities, and you produce one normalized output identity that makes sense to the system you are making your STS for. A security token service that does exactly this is often referred as a RP-STS, because it is specific for a set of RPs. An STS could also be more generic, which is true in the case of something like ADFS 2.0 (also an STS). In this case it's referred to as just an STS.

This function deals with taking in a non-normalized identity, and then normalizing or customizing it in such a way as to enrich it or stripping things from it.

Because we don't currently care about any functionality in this, we'll just make this function pass the input identity as is through the pipeline.

And with that, our Custom STS is ready to be built.

Handling the request

Let's finish off the AuthenticationController code, which is just something that takes in the request, and passes it on to the SecurityTokenService.

So to build our CustomTokenService, we have to generate a configurator. There are a couple of things that the STS will require to work, and those are the issuer name, so the URL of the STS, and secondly a reference to the certificate it'll use to sign the token upon creation. Here's the boilerplate code for reading in the certificate.

After those two things are provided, it is a simple matter of passing said configurator, the Request and the Response, our identity and our custom token service to this function:

FederatedPassiveSecurityTokenServiceOperations.ProcessRequest

Setting the certificate thumbprint in the RP config

Now there is only the small business of adding the thumbprint we took down when generating the certificate, and putting a reference to it in the RP application's web.config. So basically, the RP needs to know which certificate it can use to validate the signature it'll be receiving in the token.

And that's it. Building the token based on all this information will be done by the STS, and writing it to the response stream will be done for you. The response itself will be another WS Federation message that will be sent back to the relying party in a HTTP POST.

This POST then will be intercepted in the RP side by the WSFederationAuthenticationModule that we defined in its web.config in the previous tutorial. Then our server will respond with the site that you had in the ReplyTo parameter and it will also include an authentication cookie called FedAuth. And hey also, you just authenticated a user in the STS.

Let's look at all this in Fiddler:

The request from the RP:

The request has an action "wsignin1.0"

The response from the STS:

The response as you can see, is a form that is posted back to our RP, with the same action "wsignin1.0", but also we have this massive token in something called "wresult".

The actual token within the post:

Within that token we have:

  • Token creation and expiration
  • What realm the token applies to
  • Audience restriction, so what client this token was issued for
  • A list of user claims - so in our case just the name "test"
  • The token signature, and the algorithms used to create said signature
  • SAML specific assertion boilerplate

So the token gets posted to the root URL of the RP site (do note the form action attribute in the above html) How does the RP automatically pick that up?

Well, remember when we enabled some http modules for our RP, namely "WSFederationAuthenticationModule". Well, that thing gets this HTTP POST, does all the good stuff to read and validate the above token, and sets the HttpContext.User as a ClaimsIdentity with the exact claims that the above token has.

The post sent to the site root then responds with a Set-Cookie: FedAuth, which is basically the WSFederation authentication cookie for your current session. 

Very cool.

So now we're authenticated via the STS.

Make sure to note that all along this tutorial, we didn't change a single line of code in the forms authentication application throughout this series, and still after all this authentication logic was changed, the code we had in tutorial 1 works flawlessly. So literally, you could just turn your authentication logic to be federated rather than forms authentication without a single code change.

So we moved the authentication logic entirely to outside the application We also built the identity on the external STS, and we created a SAML token for it. We signed this token with a certificate and passed it back to the RP in a WS Federation message, which then automatically set the HttpContext.User for us, and the cookie which upholds this user data was created for us out of the box.

All this, in what is literally 4 lines of code and a bunch of configuration changes.

Next time we'll tackle on the challenge of actually making it Single Sign On, and that involves a little bit of change to the STS, because we have to maintain some user state in there to be able to sign the user in