Last week at TechDays in Toronto I ran into a fellow I worked with while I was at Woodbine. He works with a consulting firm Woodbine uses, and he caught my session on Windows Identity Foundation. His thoughts were (essentially—paraphrased) that the principle of Claims Authentication was sound and a good idea, however implementing it requires a major investment. Yes. Absolutely. You will essentially be adding a new tier to the application. Hmm. I’m not sure if I can get away with that analogy. It will certainly feel like you are adding a new tier anyway.
What strikes me as the main investment is the Security Token Service. When you break it down, there are a lot of moving parts in an STS. In a previous post I asked what it would take to create something similar to ADFS 2. I said it would be fairly straightforward, and broke down the parts as well as what would be required of them. I listed:
- Token Services
- A Windows Authentication end-point
- An Attribute store-property-to-claim mapper (maps any LDAP properties to any claim types)
- An application management tool (MMC snap-in and PowerShell cmdlets)
- Proxy Services (Allows requests to pass NAT’ed zones)
These aren’t all that hard to develop. With the exception of the proxy services and token service itself, there’s a good chance we have created something similar to each one if user authentication is part of an application. We have the authentication endpoint: a login form to do SQL Authentication, or the Windows Authentication Provider for ASP.NET. We have the attribute store and something like a claims mapper: Active Directory, SQL databases, etc. We even have an application management tool: anything you used to manage users in the first place. This certainly doesn’t get us all the way there, but they are good starting points.
Going back to my first point, the STS is probably the biggest investment. However, it’s kind of trivial to create an STS using WIF. I say that with a big warning though: an STS is a security system. Securing such a system is NOT trivial. Writing your own STS probably isn’t the best way to approach this. You would probably be better off to use an STS like ADFS. With that being said it’s good to know what goes into building an STS, and if you really do have the proper resources to develop one, as well as do proper security testing (you probably wouldn’t be reading this article on how to do it in that case…), go for it.
For the sake of simplicity I’ll be going through the Fabrikam Shipping demo code since they did a great job of creating a simple STS. The fun bits are in the Fabrikam.IPSts project under the Identity folder. The files we want to look at are CustomSecurityTokenService.cs, CustomSecurityTokenServiceConfiguration.cs, and the default.aspx code file. I’m not sure I like the term “configuration”, as the way this is built strikes me as factory-ish.

The process is pretty simple. A request is made to default.aspx which passes the request to FederatedPassiveSecurityTokenServiceOperations.ProcessRequest() as well as a newly instantiated CustomSecurityTokenService object by calling CustomSecurityTokenServiceConfiguration.Current.CreateSecurityTokenService().
The configuration class contains configuration data for the STS (hence the name) like the signing certificate, but it also instantiates an instance of the STS using the configuration. The code for is simple:
namespace Microsoft.Samples.DPE.Fabrikam.IPSts
{
using Microsoft.IdentityModel.Configuration;
using Microsoft.IdentityModel.SecurityTokenService;
internal class CustomSecurityTokenServiceConfiguration
: SecurityTokenServiceConfiguration
{
private static CustomSecurityTokenServiceConfiguration current;
private CustomSecurityTokenServiceConfiguration()
{
this.SecurityTokenService = typeof(CustomSecurityTokenService);
this.SigningCredentials =
new X509SigningCredentials(this.ServiceCertificate);
this.TokenIssuerName = "https://ipsts.fabrikam.com/";
}
public static CustomSecurityTokenServiceConfiguration Current
{
get
{
if (current == null)
{
current = new CustomSecurityTokenServiceConfiguration();
}
return current;
}
}
}
}
It has a base type of SecurityTokenServiceConfiguration and all it does is set the custom type for the new STS, the certificate used for signing, and the issuer name. It then lets the base class handle the rest. Then there is the STS itself. It’s dead simple. The custom class has a base type of SecurityTokenService and overrides a couple methods. The important method it overrides is GetOutputClaimsIdentity():
protected override IClaimsIdentity GetOutputClaimsIdentity(
IClaimsPrincipal principal, RequestSecurityToken request, Scope scope)
{
var inputIdentity = (IClaimsIdentity)principal.Identity;
Claim name = inputIdentity.Claims.Single(claim =>
claim.ClaimType == ClaimTypes.Name);
Claim email = new Claim(ClaimTypes.Email,
Membership.Provider.GetUser(name.Value, false).Email);
string[] roles = Roles.Provider.GetRolesForUser(name.Value);
var issuedIdentity = new ClaimsIdentity();
issuedIdentity.Claims.Add(name);
issuedIdentity.Claims.Add(email);
foreach (var role in roles)
{
var roleClaim = new Claim(ClaimTypes.Role, role);
issuedIdentity.Claims.Add(roleClaim);
}
return issuedIdentity;
}
It gets the authenticated user, grabs all the roles from the RolesProvider, and generates a bunch of claims then returns the identity. Pretty simple.
At this point you’ve just moved the authentication and Roles stuff away from the application. Nothing has really changed data-wise. If you only cared about roles, name, and email you are done. If you needed something more you could easily add in the logic to grab the values you needed.
By no means is this production ready, but it is a good basis for how the STS creates claims.
Using the Windows Identity Foundation to handle user authentication and identity management
can require you to drastically rethink how you will build your application.
There are a few fundamental differences between how authentication and roles will
be handled when you switch to a Claims model.
As an example if you used an STS to provide Claims to your application, you wouldn’t
(couldn’t really) use the FormsAuthentication class.
Another thing to keep in mind is how you would handle Roles. WIF sort of handles
roles if you were to use <location> tags in web.config files like:
<location path="test.aspx">
<system.web>
<authorization>
<deny users="*" />
<allow roles="admin" />
</authorization>
</system.web>
</location>
WIF would handle this in an earlier part of the page lifecycle, and only allow authenticated
users with a returned Role claim of admin. This works well for some
cases, but not all.
In larger applications we may want custom Roles, and the ability to map these roles
to the Roles provided by the STS.
This is by no means a place to tell you when you should use what architectural design,
but a lot of times we want somewhere in the middle of these extremes…
Sometimes we just want to use the Roles class to check for role membership based on
the Role claims. From what I can find there is no RolesProvider implementation
for WIF, so I wrote a very simple provider. It is by all rights a hack.
The reason I say this is because there are quite a few methods that just can’t be
implemented. For instance, getting roles for other users is impossible, or adding
a user to a role, or creating a role, deleting a role, etc. This is all impossible
because we can’t send anything back to the STS telling it what to do with the roles.
We are also limited to the scope of the roles. I can only get the roles of the
currently logged in user, nothing beyond. So, with all the usual warnings (it
works on my machine, don’t blame me if it steals your soul, etc)…
using System;
using System.Linq;
using System.Threading;
using System.Web.Security;
using Microsoft.IdentityModel.Claims;
public class ClaimsRoleProvider : RoleProvider
{
IClaimsIdentity claimsIdentity;
ClaimCollection userClaims;
private void initClaims()
{
claimsIdentity = ((IClaimsPrincipal)(Thread.CurrentPrincipal)).Identities[0];
userClaims = claimsIdentity.Claims;
}
public override string ApplicationName
{
get
{
initClaims();
return claimsIdentity.GetType().ToString();
}
set
{
throw new NotImplementedException();
}
}
public override bool RoleExists(string roleName)
{
initClaims();
return userClaims.Where(r => r.Value == roleName).Any();
}
public override bool IsUserInRole(string username, string roleName)
{
initClaims();
return userClaims.Where(r => r.Value == roleName).Any();
}
public override string[] GetRolesForUser(string username)
{
initClaims();
return userClaims.Where(r => r.ClaimType == ClaimTypes.Role).Select(r => r.Value).ToArray();
}
public override string[] GetAllRoles()
{
initClaims();
return userClaims.Where(r => r.ClaimType == ClaimTypes.Role).Select(r => r.Value).ToArray();
}
#region Not implementable
public override string[] GetUsersInRole(string roleName)
{
throw new NotImplementedException();
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new NotImplementedException();
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
#endregion
}
The next step is to modify the web.config to use this provider. I put this in
a separate assembly so it could be re-used.
<roleManager enabled="true" defaultProvider="claimsRoleProvider">
<providers>
<clear />
<add name="claimsRoleProvider"
type="ClaimsRoleProvider, MyAssem.Providers,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a27739ef3347280" />
</providers>
</roleManager>
One final thing to be aware of… Roles.IsUserInRole(string roleName) uses IPrincipal.Identity.Name
in it’s overloaded version in lieu of a username parameter which could result in this
ArgumentNullException:
Value cannot be null.
Parameter name: username
Description: An unhandled exception occurred during the execution of the current
web request. Please review the stack trace for more information about the error and
where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: username
Source Error:
Line 17: var claims = from c in claimsIdentity.Claims select c;
Line 18:
Line 19: bool inRole = Roles.IsUserInRole("VPN");
Line 20:
Line 21: foreach (var r in claims)
Since the IClaimsIdentity is getting generated based on the claims it receives, it
sets the Name property to whatever claim value is associated with the http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
claim type. If one isn't present, it will be set to null.
It took way too long for me to figure that one out. :)