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 = "https://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 (https://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.
“You can certainly come up with a poor design whereby you store too many claims in the cookie.” Seems like the recommended approach for representing custom information about the user leads one down this road. Every time someone asks about custom identities in WIF(where we could load information about the user into properties onto a class that inherited from IIdentity), the goto response is that no matter how complex that object graph, it all goes into claims now. It’s like we’re throwing out the C# object model and saying everything is a list of key value pairs. This is why AppSettings were nearly worthless and no highly configurable applications ever used them. The concrete ClaimsIdentity class is required for all aspects of the framework from storing user attributes to supporting AntiForgeryTokens, and no interface represents the requirements needed. IIdentity is there, but it is not sufficient. Inheriting from ClaimsIdentity requires understanding nuances found only in source code about which constructors cause IsAuthenticated to return true, and you have a multitude of constructors your derived class must implement. As you mentioned, this is all fruitless because it will loose your derived class and re-initialize as a plane ClaimsIdentity. Good bye interface based programming. We’re forced into putting data into claims that don’t really belong there, and then being told that’s a poor design choice when it was a choice we don’t really want to make.
You can always use claims transformation to replace the identity modeled by the framework with a custom implementation.
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: https://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/
In ASP.NET the class that manages the session lifecycle (including the creation) is System.Web.SessionState.SessionIDManager. The behavior of this class is partially controllable from the web.config configuration file in the section system.web/sessionState.
It is recommended that you change the session id name by editing the cookieName attribute.
@dewebnet — not sure what this comment about session state has to do with security or WIF. Session state is completely unrelated.
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.
@Brock
We exchanged email about this topic before, but I thought I’d throw it out here in the comments in case anyone knows what’s going on. I implemented similar code, set the federationConfiguration/cookieHandler in web.config with hideFromScript = true and requireSsl = true, I ran my site in HTTPS entirely. Everything works…except….
The FedAuth cookie is not being created with the HTTPOnly and Secure flags set to true. If you know the answer please post it, and if you want StackOverflow points, I have a bounty for this at http://stackoverflow.com/questions/16900487/sessionauthenticationmodule-cookie-handler-not-creating-httponly-secure-cookie
Brock, thanks for this blog, I implemented in a custom user name password validator following Domenics blog here: http://leastprivilege.com/2012/07/16/wcf-and-identity-in-net-4-5-usernamepassword-authentication/
However, the custom user name validator is still called on each request, I was hoping the cookie would bypass that.
My client is a WinForms WCF client, using a basicHttps binding with allowCookies=true. I can see the cookies being sent in Fiddler.
The cookie based mechanism is meant for browser based clients, not WCF clients.
Is there any way then to not have the username password validator called on every single WCF request?
I don’t know WCF well enough to answer, sorry.
in the web.config file, do i need to set authentication mode to Forms or None
Well, depends. We’re really not using forms auth, but it has a nice side effect of redirecting to the login page on 401 — so it’s up to you.
Brock,
I’ve got this implemented within a single application in which I can see all of the claims (5 in my scenario). However, if I login to one application (App 1) and then go to a second (App 2), the ticket is still valid, but the only claim I see is “name” issued by “Forms”. Going back to “App 1” reveals the original 5 claims created during the initial login. I have the same machine key across the two applications. What I’m trying to get to is a centralized login that populates all of the claims required to identify the user and allowing these claims to flow to the other applications that are accessible from the main site thus allowing these applications to make use of the claims and then do the necessary access checks, etc.
Is this the right way to think about this or is there a better way? I’d appreciate your insight into this.
If you want the SAM cookie to be shared on each app on the machine, then set the cookie path to “/” and you’d need to configure the SAM http module in each app. If you need those to be supported in a farm, then your machine key will be needed, as well as this: https://brockallen.com/2013/02/18/configuring-machine-key-protection-of-session-tokens-in-wif-and-thinktecture-identitymodel/.
As far as “is this the right way”, I suppose it depends. Since you’re not using a full blown STS, then your approach works.
Thanks Brock. The cookie path was exactly the thing I was missing.
I’m working on an app that has recently done just this using forms. However, we’re now converting it to use Windows Authentication.
So basically, I’ve turned on Windows Auth in the config, and I’m populating additional roles by adding them here as you’ve done, but neither AuthorizeAttribute or User.IsInRole() work with the custom roles I’ve added as claims.
I noticed that with the WindowsClaimsPrinciple that the RoleClaimType is set to GroupSid so I tried changing that to Role and also tried adding my custom roles as GroupSids but neither have worked. Also, in some instances of User.IsInRole() I’m getting back a domain trust error: “The trust relationship between the primary domain and the trusted domain failed”.
(If pushing the network admins to help resolve this issue would fix my problem, then I could do that.. but they are undergoing an AD migration of multiple subs to an eventual single domain. currently all users exist in the single but are disabled. But I’d like to know that the code /should/ work before having them chase the trust issue.)
Did you see this post: https://brockallen.com/2013/01/17/adding-custom-roles-to-windows-roles-in-asp-net-using-claims/
Anyway, I’d suggest playing around with how you do it. Consider either adding a new Identity to the Principal class (instead of just adding claims directly to the existing Principal/Identity), or replacing the principal with a custom one (as in this post) but keeping the claims from the windows identity.
HTH
I did, and I was trying to use both of these posts together. I think the crux of my issue might be that I’m not using 4.5, but 4.0. (There are some big differences between Microsoft.IdentityModel & System.IdentityModel)
This is an MVC3 app using EF 5 (with the 4.0 subset). Moving to 4.5 introduces a great many namespace problems with EF. Moving to EF 6.0 solves those but introduces others, etc. If at all possible, I avoid that upgrade path and stick with 4.0.
I’ll try a few more things, like overriding the default principal. When I did this before, taking things further by setting a cookie to avoid database calls every request, I was getting errors like the token could not be found/loaded or reused trying to use persistent cookies, etc.
I see. And the roles from the windows identity aren’t really exposed to just copy over into a new principal. You might get away with wrapping the old identity — IOW, your IsInRole check would check your claims identity and then also check the “inner” wrapped WindowsIdentity. G’dluck :)
Thanks for the encouragement ;)
I spent all day tumbling down the rabbit hole of updating to 4.5, EF 6, etc, and everything seems to be working well now.
Now I just need to get this cookie action working so we don’t query the database on every request, and then provide a means for users to logoff (delete the cookie & force a browser auth prompt).
With respect to the cookie, previously we had web.config settings for federatedAuthentication under Microsoft.IdentityModel, but this is now gone and the replacement System.IdentityModel is completely different. If not in the web.config, where do I set the ‘is SSL required for cookie’ settings?
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl=”false” />
Great article.. I tested as said in this article, but receiving system.null exception error when executing the code: sam.WriteSessionTokenToCookie(token).. I checked the token also it not null …
Perhaps you don’t have the SAM registered in the modules section of web.config?
how can i read if the token cookies is already created ?
I don’t understand the question.
ok.. let me explain ,, I have an asp.net application which is claim aware, and i have sharepoint which is configured with WIF, so when i login to the asp.net application the security token is created and i am able to login to sharepoint site directly with out login again, but when if i am still in the sharepoint site means did not logout, how do i know that i have to create the signin request again or no.
hope you understand .
I
When you do SSO you have to realize that you are logged into each app *and* the SSO web site as well. That’s how SSO works — when you go to a new app to login, the SSO server knows you’re still logged into it and thus just issues the token for the app. The SSO server usually is using a cookie to keep track of the user.
Thanks for your response,
but how do i know that the cookies still exist or not.
Regards
Use your browser’s F12 developer tools.
Thanks for your continuous response. but still i am lost .. below is description and the code
I have a sharepoint website, which I have configured with WIF (window indentity foundation) , now my aim is to use Single Sign On (SSO) between my asp.net application and sharepoint, When I try to click on the sharepoint link inside my asp.net application it works and did not show me the login screen of the sharepoint site, but if some body click again that sharepoint link from the asp.net application it show me the login page of the sharepoint site, until I am signout from the sharepoint site.
Below is the code I am using to do Single Sign On (SSO) from my asp.net application.
Uri uu = new Uri(“http://sharepoint2010n:1001/default.aspx?wa=wsignin1.0&wtrealm=http://sharepoint2010n:8003/_trust/&wctx=http://sharepoint2010n:8003/_layouts/Authenticate.aspx?Source=%252F&wreply=http://sharepoint2010n:8003/_trust/default.aspx”);
SignInRequestMessage requestMessage = (SignInRequestMessage)WSFederationMessage.CreateFromUri(uu);
if (User != null && User.Identity != null && User.Identity.IsAuthenticated)
{
SecurityTokenService sts = new CustomSecurityTokenService(CustomSecurityTokenServiceConfiguration.Current);
SignInResponseMessage responseMessage = FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest(requestMessage, User, sts);
string s = responseMessage.WriteFormPost();
this.Response.Write(s);
}
else
{
throw new UnauthorizedAccessException();
}
This sounds more like a WIF issue — I’d suggest using the Microsoft support forums or contacting Microsoft support directly.
Ok.. thank you
Hi Brock,
Recently implemented this into an application we made, and it’s working great.
However, we’ve had a security audit done, and they said the application was vulnerable to cookie replay attacks, even after the user had logged out.
Apparantly, doing ‘FederatedAuthentication.SessionAuthenticationModule.SignOut()’ removes the cookie clientside, but still keeps the token in the local server cache.
Do you have any idea what’s going on, or know how to fully remove a client’s token + claims from the server?
I’m not sure which issue the audit is talking about, because you’re discussing two issues here. One is the cookie replay and the other is the stale entry in the cache.
For the stale entry in the cache, I wasn’t aware of this, but I suppose it makes sense. What if you had a web farm? The user is only on one machine when they logout and if they removed the cache entry from that server you’d still have other servers with the stale entry. You’d have to do a lot of work to make all the caches consistent (assuming the default in-memory cache). I imagine the thinking is that the stale entries will be purged eventually based upon other users’ activity (since IIRC it’s a MRU cache).
As for the cookie replay — this is no different in WIF with the SAM than it is with any other cookie approach. To prevent the cookie from being replayed after the user has logged out you’d have to keep a copy of that cookie on your server and check on every request into your server if that cookie was marked as being logged out. This would get harder in a web farm scenario as well. So while this sounds nice I think there are some issues that makes it difficult in practice.
Did your audit provide suggestions on how to address the concerns?
With Forms Auth, I use a tag in web.config to put a custom section on the “Private” views in my MVC 4 app. Basically, it just denies anonymous users and redirect automatically to the login page defined in the part. So it’s quite easy to manage private vs public access on a site. Is there a problem if we keep the FormsAuthentication cookie AND add the SAM cookie too ? If I use the IsReferenceMode for this one, it should stay small enough anyway ?
Otherwise, would you suggest me to drop all this Forms Auth mechanism altogether and manage serving views in the controllers ?
If you used both they’d step on each other, since there’s only one “User” in ASP.NET. As for your authorization — that’s separate from authentication.
Thanks ! I tried the approach from you colleague Dominick (http://leastprivilege.com/2012/02/09/replacing-asp-net-forms-authentication-with-wif-session-authentication-for-the-better/) and it works very well. Had to adjust a little because we have a SPA but it wasn’t too hard.
Great article, was an awesome jump start.
Is there a way that I can set the auth cookie to expire automatically on browser close, per a normal session cookie?
Yes, that’s just the persistent flag when you issue the cookie with the session authentication manager (SAM).
Thank you. Thank you. Thankyou.
Hi Brock. I am trying to build a modular application using Areas in ASP.NET whereby a user must be authenticated against an Area to make sure he/she has access and then accordingly the module/area would assign the user a claim. What is it called this type of authentication? Any guidelines on how to build such a solution? Many thanks
I found this article while looking for a tutorial of WIF in ASP.NET MVC. There is a tutorial for this type of setup at https://msdn.microsoft.com/en-us/library/hh291061%28v=vs.110%29.aspx but it is for MVC 3 and Visual Studio 2012. I am using Visual Studio 2015 with .net 4.6.