SSO with Windows Identity Foundation: Part V - SSO
Welcome back to the wondrous world of authentication.
In this part we will implement SSO.
And here's the kicker, you won't believe how simple this bit is - really, you won't believe it.
Without further ado, let's start by opening what we made last time:
All of the code for this part can be viewed at github.
Session authentication
So, as you know - in SSO we simply hold the master data about whether a user is authenticated or not in a single domain, and our clients can then check from this domain if the user already logged or not. In our case this master domain is the STS.
Start by opening the STS project, and enabling the SessionAuthenticationModule in Web.config:
We'll use Session Authentication to hold the user state. We could just as well use Forms Authentication if the only thing we were saving was the username, but in this case we'll stick to the modern stuff.
After having enabled that, we'll copy the Forms Authentication Rules project into two separate clients so we can test our project.
I made two copies of the Forms Auth project and named them Client 1 and Client 2 respectively. You can view the clients over at github.
Going Coo coo on Cookies
I'm gonna spoil how this is all supposed to work:
- Client 1 sends a request to STS for authentication
- STS runs FederatedPassiveSecurityTokenServiceOperations.ProcessRequest, but what it also does is it writes a cookie into the response for it's own domain, which will hold the user data for the STS's domain.
- STS responds with WS federation message and an auth cookie for the STS's domain
- Client 2 then sends a request to the STS for authentication
- The browser already has a cookie for the STS's domain, so it gets the user data from the cookie rather than the user store, and it returns the same WS federation message.
The problem with the above is that in our projects we've been using IISExpress so far, and all of our sample projects run in the same domain (localhost), and any cookies would simply be interchangeable by default. Next step for us is to make the STS run in some sort of domain.
Hosts file:
With IIS Express:
- Write the above into the STS-project's settings and press Create Virtual Directory
- Open C:\Users\Username\Documents\IISExpress\config\applicationhost.config
- Find the binding for STS, and change the binding to ":80:sso.local"
- Add this line to your hosts file: 127.0.0.1 sso.local
With IIS
- Create an IIS site with the binding http://sts.local/ and point said site to your STS folder.
- Add this line to your hosts file: 127.0.0.1 sso.local
- In visual studio, change the STS project's settings to this:
Great. Now you should have your clients running on localhost and your STS should respond from http://sso.local/
Go ahead and change the issuers on both clients web.config to be http://sso.local/
At this point, running either of your clients should still work as before, only now the redirect is done to http://sso.local/ which still responds equally with the WS federation message.
For the clients, follow these steps to also set them to custom URL:s if you like.
Down to business
So, the only thing left to do is actually finish off the STS to hold some state. Here's how we do this:
Then, wire that up in our AuthenticationController:
Boom, done.
So as you can see, the only difference to last time is that now we also write an auth cookie for the STS's domain in addition to making the WS response. That way, the user state is upheld on the STS for further authentication requests.
To test this, you can run the STS project in debug mode, and put a breakpoint to the Authenticator's private functions. Then when the redirect occurs you can first see that the user is created from our mock user (in production you'd have a login form post us some credentials, and a database to get the claims from), but if you now remove the cookies for the client's domain and refresh the page, then the next request will be authenticated from the cookie that was set for the STS's domain.
You can also repeat the steps for Client 1 and Client 2 where you assign them their own separate domains just like we did for the STS, and you can then easily see how one of them logs in when you run it, and the other uses the cookie that was already set for the STS's domain.
The only thing you need now for production is a login screen on the STS which would post the credentials, check a database and create the ClaimsIdentity that way, and otherwise it's the same code more or less.
I will continue on this topic if there is interest. thanks for reading!