Replacing forms authentication with WIF’s session authentication module (SAM) to enable claims aware identity
Forms authentication was great. For like 10 years it was great. But it’s time for us to move on…
The main issue with Forms authentication is that the forms auth cookie was primarily only designed to keep the user’s username and no additional data (despite the UserData property and the unfortunate lack of APIs to assist populating it and managing the ticket and cookie). So to augment the username with roles or additional identity data (typically from the database) we would use the PostAuthenticateRequest event in ASP.NET/IIS (as discussed here and here). The main issue with PostAuthenticateRequest is the additional round trip to the database on each request into the web server. Caching can help mitigate this, of course, but you have to explicitly do the caching.
Now that .NET 4.5 is claims-aware, I’d submit that using forms authentication is antiquated. Dominick already posted on this a while ago, but I wanted to show what’s involved from a more introductory perspective. I’d suggest reading Dom’s post for more motivation. I’ll show here how to use the SAM directly.
This is what traditional login code could would look like with Forms authentication:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username, string password)
{
if (DoDatabaseCheckToValidateCredentials(username, password))
{
// set username in cookie, false issues non-persistent cookie
FormsAuthentication.SetAuthCookie(username, false);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
return View();
}
This would simply log the user in. We’d then have to load their roles or claims on each and every request as such in global.asax:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var ctx = HttpContext.Current;
if (ctx.Request.IsAuthenticated)
{
string[] roles = LookupRolesForUser(ctx.User.Identity.Name);
var newUser = new GenericPrincipal(ctx.User.Identity, roles);
ctx.User = Thread.CurrentPrincipal = newUser;
}
}
So with WIF 4.5 they have a different http module that will do what the forms authentication http module does, but it’s claims aware. This means it allows you to assign claims (and thus roles) at login time. This identity information is cached in the cookie and therefore you won’t have to re-query the database upon each subsequent request. Here’s the code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string username, string password)
{
if (DoDatabaseCheckToValidateCredentials(username, password))
{
Claim[] claims = LoadClaimsForUser(username);
var id = new ClaimsIdentity(claims, "Forms");
var cp = new ClaimsPrincipal(id);
var token = new SessionSecurityToken(cp);
var sam = FederatedAuthentication.SessionAuthenticationModule;
sam.WriteSessionTokenToCookie(token);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
return View();
}
So as you can see, we do the same check against the database to validate the credentials. We then query the database for the identity information about the user and this is returned as an array of Claim objects. We then create a identity and principal from the claims and then create the token and write it to the response. On subsequent requests the cookie will be read and populate our user object.
A few details to fill in. Here’s what the code to create the claims might look like:
const string OfficeLocationClaimType = "http://brockallen.com/claims/officelocation";
private Claim[] LoadClaimsForUser(string username)
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, "username@company.com"),
new Claim(ClaimTypes.Role, "RoleA"),
new Claim(ClaimTypes.Role, "RoleB"),
new Claim(OfficeLocationClaimType, "5W-A1"),
};
return claims;
}
The idea is that you can create any claims you’d want to model the user’s identity for you application (including custom claims like the last one in the list). To access the claims on subsequent requests, you’d use the old non-claims aware APIs or the new claims-aware APIs (both work against the same identity information):
void DoSomeStuff()
{
// old style role check
if (User.IsInRole("RoleA"))
{
// old style username check
var name = User.IsInRole.Name;
// new style claim check for the email claim
var email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
// use the identity information
SendUserEmail(name, email);
}
}
The only last thing to mention is the configuration needed to do all of the above. First your project will need references to System.IdentityModel and System.IdentityModel.Services. And then you’ll need to add some <configSections> to web.config. And lastly you’ll need to add the SAM (session authentication module) to the http modules list:
<configSections> <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /> <section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" /> </configSections> <system.webServer> <modules> <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </modules> </system.webServer>
And there we have it. Happy modern authentication!

Storing claims into the cookie does not really look too efficient. Only storing the username, email address and one role claims created a cookie of almost 1Kb which will be sent on every request from browser. Are there any other ways to store claims which can trim down or offer an alternative to cookies?
@Robert — It’s about tradeoffs. You can certainly come up with a poor design whereby you store too many claims in the cookie. You still have the PostAuthenticateRequest as an option (http://brockallen.com/2013/01/17/adding-custom-roles-to-windows-roles-in-asp-net-using-claims/) so you could have a mix of the two models. Lastly, the SAM supports a flag called “IsReferenceMode” where the cookie only contains an identifier and then the claims are automatically cached in the server (which is customizable as needed for web farms, etc.).
As I see it, the main benefit is the claims-aware nature of the SAM as the authentication layer.
I cannot get this sample to write a cookie to the response. Is there a config setting missing? Can you post the source of your sample?
Use SSL or put this in web.config:
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl=”false”></cookieHandler>
</federationConfiguration>
</system.identityModel.services>
But you really should be using SSL.
That worked. Can I use this across 2 websites in azure? I am doing so with forms auth and same machine key.
Yep, you can use the cookie across a web farm as long as they’re all using the same key. You need to register the machine key session security token handler:
http://msdn.microsoft.com/en-us/library/system.identitymodel.services.tokens.machinekeysessionsecuritytokenhandler.aspx
Hey Brock, I have a site hosted in Azure that is using a custom security handler to leverage RSA/SSL instead of machine keys. This works fine.
However, I have not enabled IsReferenceMode because I’m leery of writing my own SessionSecurityTokenCache implementation. I did find http://code.msdn.microsoft.com/vstudio/Claims-Aware-Web-Farm-088a7a4f but am not interested in taking a dependency on WCF.
I suppose I could something similar using WebAPI. Do you guys have any plans to do anything in this space with the Thinktecture IdentityServer and IdentityModel?
P.S. I have watched Dom’s Pluralsight courses and wish he went into more detail on these topics. Maybe there should be a third, advanced course. ;)
Yes, I plan to have a token cache soon in the Thinktecture.IdentityModel project (in the next 2-3 weeks as time permits). Stay tuned.
Fantastic. Thanks for the quick reply.
I would be interested in a token cache using azure’s distributed caching. I am not finding any examples out there
Brock will this approach work to secure things like elmah and using the web.config to block access? Trying to determine what needs to be replaced if choosing this over Forms
This doesn’t affect how you do authorization. This is only the authentication piece.
I’m curious why no mention of RolePrincipal? It uses its own cookie rather than hitting the persistence layer on each request, and is claims aware in .net 4.5. And IIRC, a recent patch has also made .net 4.0 RolePrincipal claims aware. Perhaps the answer is “no point in mentioning a mechanism that violates SRP by tying auth with persistence?”
Brilliant – thanks Brock!
Just to consider – you might want to note the RequireSsl part in your post, Devs typically don’t use SSL during development. I just spent an hour hitting this wall until something clicked to try RequireSsl = false. :) Thanks again.
Brock, could I ask a question if/when you have a chance?
What is your recommendation if someone wants to use the SAM, but also wants two traditional forms auth goodies: redirect to login (and redirect back) + sliding expiration?
I tried using the FAM for redirects, but things got complicated quickly – and I felt like I was in overkill world, since my app isn’t federated.
Many thanks.
For redirect you can leave forms auth enabled pointing to your login URL. You’d just never use FormsAuth to issue the cookie (you’d use the SAM instead). Or you can just handle EndRequest and look for 401 and redirect manually (since that’s all forms is doing for you).
For sliding sessions you can use the sliding session support I added to Thinktecture IdentityModel: http://brockallen.com/2013/02/17/sliding-sessions-in-wif-with-the-session-authentication-module-sam-and-thinktecture-identitymodel/
Thank you!
I tried doing this in the past and had a few issues but after finding your post I’m going to give it another shot. However I found that if I set a name to the cookie handler via the web.config (), my login stops working. Any idea why customizing the cookie name would cause this?
Nope, sorry. IdentityServer does this, so maybe look at it for inspiration:
https://github.com/thinktecture/Thinktecture.IdentityServer.v2/
I just added your example to an app as a precursor to eventually moving over to IdentityServer as an STS but I’m getting odd behavior from this implementation. I’ve set a duration of 5 seconds on the SessionSecurityToken but it seems to have no impact. Is there some other factor being used to determine the duration of the authentication?
Internally they have a 5 minute clock skew that they use, so if you set the duration for 5 seconds, then it will really be 5 mins and 5 seconds. You can change that clock skew in config.
That’s exactly what my problem was. After setting the max clock skew to 0 it behaved as expected. I changed it in the web.config at the following path
>system.identityModel<>identityConfiguration<>securityTokenHandlers<>securityTokenHandlerConfiguration maximumClockSkew=”0″< retrying with escaped HTML, feel free to delete if this doesn’t work.