In Part 1 of this series - Single Sign On Between Sitefinity And Third Party Applications: The Basics - we discussed the essential terminology and some of the basic technology that is in play when maintaining Single Sign On scenarios between Sitefinity and 3rd party applications. In this post we are going to get into deeper specifics around the case where Windows Identity Foundation is utilized to facilitate the communication between Sitefinity and another application, obtain a token and work consistently with users who authenticate using this token. This is an approach that I tend to evaluate first for each architecture, since I prefer recommending an architecture based on WIF – it is suitable for both enterprise and social scenarios, it involves minimal coding, creates more flexibility, it’s very secure and generally doesn't require you to be concerned with transportation layer specific. On the other end however, this approach imposes limitation on what the actual STS should be. In a nutshell your STS needs to expose ws-federation metadata in some way shape or form OR (as we will see in one of the tricks described here) it needs to provide us with a way to embed our own STS logic directly.
Basics
In this post I want to take a look at 2 potential application scenarios for SSO.
- Achieving SSO between Sitefinity and a 3rd party app through a 3rd party STS( proprietary STS or any service such as Azure ACS)
- Achieving SSO between Sitefinity and an ASP .NET app by making the MVC app the STS.
Part 1 is where we will look at the most common scenario: having two completely heterogeneous apps that will talk to each other through means of a Security Token Service that will authenticate and provide tokens for both. This includes a few internal steps and redirects generally invisible to the user but crucial for passing down the identity in a secure and properly formatted way.
In the second part we will see a nice trick to build out an architecture where a third party app built in ASP .NET (could be MVC, or WebForms or a hybrid of both) houses and reuses the Sitefinity STS code and therefore in a centralized fashion controls the identity of users, therefore making one of your apps an STS to the other.
Don’t consider this blog post a step by step guide. To an effect it provides a complete overview of the approaches and these are architectures that have worked for certain organizations where certain considerations and specifics have been answered. But with that said, each organization has their own unique SSO infrastructure and policies and therefore incorporating Sitefinity or any other application is in any case doable, but case-specific - it requires a good plan that could be a combination or morph of the approaches laid out below.
So there is plenty of good reading ahead, let’s get started, shall we:
Using an external STS
The architecture here assumes that you already have an STS and your third party application is configured to use it. This could be Azure ACS, it could be any proprietary issuer you have built in. The token format that it issues could be SWT, it could be SAML or any other custom format needed and Sitefinity can support this because we will use the STS as a translator.
The workflow that happens in this solution is the following:
1) A user requests a resource that is restricted. This could be the Sitefinity back-end, or any page that requires authentication or doesn't provide view permissions to everyone.
2) Sitefinity is configured to federate to the Sitefinity STS. In other words the STS is intended to be the party that handles the authentication(either against AD using windows auth, or through forms auth or any other fashion) and Sitefinity is not concerned with the specifics, it is just the relying party that authorizes the user when the claims received back from the STS.
3) Here comes the trick: we are going to keep this same configuration, Sitefinity is the relying party of the Sitefinity STS, but we are going to make the STS a relying party of your proprietary STS and read all incoming claims from there. This way the Sitefinity STS serves as the middle man between the needed parts of the infrastructure.
4) Now when the user is redirected to the Sitefinity STS, he now becomes automatically redirected to the proprietary STS that you have in place and this STS can then do windows authentication, show a form to login, authenticate using social login or anything else, it doesn't really matter.
5) Once this redirects to the Sitefinity STS, the handler code now has an actual claim and this claim is something that the STS translates using Sitefinity’s SWT format and passes back to Sitefinity.
6) Since we now have a token and know who the user claims to be, we need to match this user's within a membership provider. This may be a part where the actual implementation depends a lot on how you handle membership and what type of features would you like in the Sitefinity UI. For some cases like LDAP or default ASP .NET SQL membership provider it’s just adding a few lines of configuration, for other – it can require some custom provider code, for which we will go into more details later.
This is a diagram that exposed the 3 different steps in the process:
Now here are the implementation specifics:
For the different steps described
1) Configure Sitefinity just as you would for Windows Authentication(Described here)
2) To test things out even set up Windows Authentication and LDAP properly and make sure that everything works correctly. Even if your Sitefinity application is running on Azure for example, you will benefit from doing all this work on the network, until all the pieces are in place and can be deployed anywhere else.
3) Now that windows authentication works – we will break it. Change the authentication mode to your STS web app to anonymous only and add an STS reference to wherever your STS might be. Here Visual Studio 2010 and 2012 work differently. VS 2010 has the Add STS reference menu.
In VS 2012 this is replaced by the Identity And Access Menu:
4) Now what this does is take us to a wizard where we should set up the STS that we will be working with. If this is ADFS, ACS or any other STS it should expose WsFederatiaon metadata that we can point to. Here are two lengthier tutorials on how to set that up using azure ACS for VS 2010 and VS 2012. If we are not dealing with ACS, then you just have to point to your federation metadata exposed by your service.
5) Now that this step is ready, we will need to make a few slight modifications to the Sitefinity code to make everything work correctly.
Modifications to the STS web app source code
Let’s take a look at the anatomy of the STS web app for a second. If you look at it closely the key component is the SimpleWebTokenHandler class.
The only lines that need changing can be found in the SimpleWebTokenHanlder.ProcessRequest() method. Take a look at this line:
var winPrincipal = context.User
as
WindowsPrincipal;
Here we get the current authenticated user and we assume this is running windows authentication and therefore cast the current user to a WindowsPrincipal. If this isn’t true and the site is running a different type of authentication we need to change the cast to ClaimsPrincipal. So instead we should use the following line instead:
var principal = ClaimsPrincipal.CreateFromPrincipal(context.User);
Next when instantiate the identity we would need to use the following code:
var identity = ClaimsPrincipal.SelectPrimaryIdentity(principal.Identities);
Everything else is just about the same.
Making sure the claims are in place
Here the crucial step is to inspect the claims that you get in this case in your Identity.Claims array. Since we are not using the default STS web app and a different type of authentication we may have to manually inject the claims that Sitefinity wants from us. The 2 critical claims that we need here are:
1) Name Claim:
Type: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
Value: The username of the user
2) Domain Claim:
Type: "http://schemas.sitefinity.com/ws/2011/06/identity/claims/domain"
Value: The actual membership provider that you are authenticating in Sitefinity(probably LdapUsers)
In debug mode inspect the claims that are coming in to the STS web app back from your identity provider and see if those two are present. If not add you can programatically add them directly in the process request method after you instantiate the identity using identity.Claims.Add(new Claim(…))
Now in ACS, or technically any STS that is more sophisticated, we can add or map claims directly through the UI through so called claim rules, the only thing that you need to make sure is:
a) A unique identifier, such as a Gmail address, or an AD sAMACcountName, is passed with the name claim
b) A string representing exactly the name of your membership provider is passing as the domain claim
Having these in place should make you be able to log in and test your infrastructure. To test if everything is flying correctly, try just passing “Default” as the value of the domain claim and create a user in your default membership and role provider with the same name you are passing through your name claim. If you have given this user any type of back-end access, then this should be sufficient for you to access the back-end when you navigate to /Sitefinity.
Sitefinity actually has schemas for a couple of other claims we use internally and just for your information here you can see all their types:
public
const
string
TokenId =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/tokenid"
;
public
const
string
UserId =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/userid"
;
public
const
string
Domain =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/domain"
;
public
const
string
Role =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/role"
;
public
const
string
IssueDate =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/issuedate"
;
public
const
string
LastLoginDate =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/lastlogindate"
;
public
const
string
Adjusted =
"http://schemas.sitefinity.com/ws/2011/06/identity/claims/adjusted"
;
Hopefully all our claim ducks and redirects are now in order and we are ready to tackle the last piece of the puzzle, the provider.
Creating the Membership provider
If this was an internal site and we were doing SSO based on Windows Authentication for example, it would be sufficient to just configure the LDAP provider and all of the intended scenarios will work. Since our scenario is a bit different and more sophisticated than that, then probably this calls for more consideration on what the membership provider should be – either LDAP, a default SQL Membership and Role provider or a custom provider for the purpose of connecting the user entity federated from the internal database or a social network to a user object in Sitefinity. In any case all we need to do is plug in a provider. You can refer back to Part 1 to review more details on providers, but in effect they provide pluggable data source logic. A provider is a class that inherits from either MembershipDataProvider(a Sitefinity base class) or MembershipProvider( a .NET base class). We use the Sitefinity class internally but there is a wrapper for any standard .NET provider including the Default SQL membership and role provider as described in this blog post. We recommend using MembershpDataProvider if there is no apparent reason to use the .NET class, because otherwise you would have to pass a userId claim for your site as well.
Essentially when you create this provider you will need to implement just a couple of methods in order for Sitefinity to work. You will need to fetch a user by id or name or a list of users from whatever data store you have. A provider in our context looks exactly like this:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Telerik.Sitefinity.Security.Data;
namespace SitefinityWebApp.Widgets { public class CustomMembershipProvider : MembershipDataProvider {
//implement these to get the data for single users. public override Telerik.Sitefinity.Security.Model.User GetUser(Guid id) { throw new NotImplementedException(); }
//this is a method that Sitefinity will try to use in the Claims resolver public override Telerik.Sitefinity.Security.Model.User GetUser(string userName) { return base.GetUser(userName); }
//if possible - implement this method to get your users lists. This will enable you to do user/role and permissions management from the UI public override IQueryable<Telerik.Sitefinity.Security.Model.User> GetUsers() { throw new NotImplementedException(); }
//leave the below ones not implemented. public override Telerik.Sitefinity.Security.Model.User CreateUser(Guid id, string userName) { throw new NotImplementedException(); }
public override Telerik.Sitefinity.Security.Model.User CreateUser(string userName) { throw new NotImplementedException(); }
public override void Delete(Telerik.Sitefinity.Security.Model.User item) { throw new NotImplementedException(); }
} } |
That’s it, nothing too complicated. It depends on how well you want Sitefinity to work in your infrastructure, you can get away with minimal implementation – for example GetUser can return a User object instantiated with values from the current claims and nothing more. But you will benefit if you expose a communication protocol like a REST service with your data store at least for these methods, for this way the users that you store and manage separately, can also be managed as a part of the powerful Sitefinity permissions. You will benefit from having GetUsers since this will ensure that all your backend user lists will be populated and you can assign permissions to your internal users using the UI. If you don’t implement GetUsers then you would probably need to implement some sort of a screen that assigns permissions to users using the API. In essence the only thing that Sitefinity needs is that:
a) The name claim passed from the STS matches the username. It will internally call GetUser(username) to return a user object
b) The domain claim that is passed is the same as the name with which you register this membership provider. GetUser will be called directly in the context of that provider.
From then on we have a claims resolver that takes care of the rest and logs in our user.
One thing that strikes as obvious is that Guids are in use for membership providers. Sometimes your providers have other unique identifiers and no guids. A handy trick is to have a method that uniquely defines a GUID based on the username token. This way you can resolve the id’s based on the usernames using MD5 hashing for example and use that to fetch an id through name every time as well.
Alternatively a topic we discussed in the first blog post is the different strategies to tackle the provider question. If you don’t want to implement a membership provider you could either:
1) Just work with the fact that your users are front end users and hence not subject to permissions
2) Map your users in the Sitefinity database.
- This can be a manual process – let’s say you have 10 contributors connecting using ADFS. It doesn’t take too much time to set up accounts with the same names
- It can also be fully automated. Every time a user hits register, we create the needed account. And then login works as expected. You can see an example for this in the Sitefinity Oauth integration project.
And that’s about it. Find a way to obtain a name claim, a domain claim and have a membership provider and you are good to go.
Embedding this in an ASP .NET site
Do you know how Single Sign On works between different Sitefinity sites? We have an embedded STS within Sitefinity itself. That is what the (~/Sitefinity/Autenticate/SWT) thingy you see is whenever you authenticate. So effective one Sitefinity site acts as an STS to the other(s).
Now having this in mind, diagram above seems a little bit complicated to me. Let’s try and fix it a little bit:
There. Much Better.
The only thing left of facilitating this type of architecture and reducing the number of steps is to actually find a way to incorporate the Sitefinity STS into a 3rd party application(or a 3rd party STS for that matter).
In order to do this, all that’s left to do is to take apart the Sitefinity STS and incorporate the pieces in your app. If you take a look the Sitefinity STS is really not that complicated to begin with. It is just an HTTP Handler with some helper classes to form a token and it’s registered in web.config to be mapped to the route ~/mysts.ashx
So if you copy this entire code anywhere in an ASP .NET app you will effectively be redirecting from Sitefinity to an STS app for all aims and purposes, but it will authenticate like your app, because it will be, in fact, your app.
So one thing that strikes as obvious in the SimpleWebTokenHanlder class is the following. We check if the user is null and then throw an exception.
if
(winPrincipal ==
null
|| !winPrincipal.Identity.IsAuthenticated)
throw
new
ConfigurationException(
"This web site is not correctly configured for Windows authentication."
);
This makes sense for windows auth, but now we are embedding this code into a different application, then it will blow up for every user that is not authenticated yet. No Bueno.
In this case we can modify the approach just a little bit. Instead we should be redirecting to any form that you have configured that actually authenticates users externally and then redirect back to this handler once authentication is successful. This is logic that you would have to implement in the part of your application responsible for this. Instead in this if we can issue a redirect, that will return back to our handler, once the login is complete.
If you are to embed the handler and all it’s helper classes or similar logic to your MVC application, remember to exclude .ashx from your routes.
routes.Ignore(
"{resource}.ashx/{*pathInfo}"
);
Here is a GitHub Project that helps with the showcased ideas and exposes modified code for the STS WebApp to fit a different application scenario (Forms Authentication using the SQL Membership and role provider). You can use this to check on some of the modifications to the STS web app that we referred to and see how simple redirect logic can be implemented as well.
Final Words
SSO is definitely not a piece of cake and coming up with a proper infrastructure for your organization can require a pen and pencil and a quite a few days’ worth of research. It is worth mentioning that Sitefinity is capable of meeting these enterprise or social scenarios and there are just a few pieces that you need to configure as a part of that. From an architectural perspective it is worth continuing to explore different tricks, solutions and concepts, we will add other ideas in upcoming blogs as well and please feel free to share with us your thoughts or feedback as to what has worked for you and what has proven to be more difficult to setup or maintain.
Happy Federating!