Skip to content

Replacing forms authentication with WIF’s session authentication module (SAM) to enable claims aware identity

January 26, 2013

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!

66 Comments leave one →
  1. Robert Mircea permalink
    January 26, 2013 2:43 pm

    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?

    • January 26, 2013 2:56 pm

      @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.

      • Aaron Shumaker permalink
        March 22, 2016 4:16 pm

        “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.

        • March 24, 2016 10:18 am

          You can always use claims transformation to replace the identity modeled by the framework with a custom implementation.

  2. January 27, 2013 10:40 pm

    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?

    • January 27, 2013 10:45 pm

      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.

  3. January 27, 2013 10:51 pm

    That worked. Can I use this across 2 websites in azure? I am doing so with forms auth and same machine key.

    • January 27, 2013 10:57 pm

      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

      • David Peden permalink
        January 28, 2013 1:33 pm

        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. ;)

        • January 28, 2013 2:26 pm

          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.

          • David Peden permalink
            January 28, 2013 2:41 pm

            Fantastic. Thanks for the quick reply.

          • January 29, 2013 10:00 pm

            I would be interested in a token cache using azure’s distributed caching. I am not finding any examples out there

  4. January 28, 2013 4:43 pm

    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

    • January 28, 2013 7:04 pm

      This doesn’t affect how you do authorization. This is only the authentication piece.

  5. condigno permalink
    February 18, 2013 10:57 am

    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?”

  6. March 24, 2013 1:23 am

    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.

  7. djclausen permalink
    April 3, 2013 5:23 pm

    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.

  8. Nick permalink
    May 13, 2013 4:17 pm

    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?

    • May 14, 2013 6:17 pm

      Nope, sorry. IdentityServer does this, so maybe look at it for inspiration:
      https://github.com/thinktecture/Thinktecture.IdentityServer.v2/

    • July 23, 2013 1:24 pm

      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.

      • July 23, 2013 1:50 pm

        @dewebnet — not sure what this comment about session state has to do with security or WIF. Session state is completely unrelated.

  9. yarxcom permalink
    May 14, 2013 1:51 pm

    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?

    • May 14, 2013 6:19 pm

      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.

      • Nick permalink
        May 14, 2013 6:38 pm

        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

        • Nick permalink
          May 14, 2013 6:40 pm

          >system.identityModel<>identityConfiguration<>securityTokenHandlers<>securityTokenHandlerConfiguration maximumClockSkew=”0″< retrying with escaped HTML, feel free to delete if this doesn’t work.

  10. June 13, 2013 4:06 pm

    @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

  11. John Waters permalink
    November 2, 2013 4:26 pm

    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.

    • November 2, 2013 7:09 pm

      The cookie based mechanism is meant for browser based clients, not WCF clients.

      • John Waters permalink
        November 2, 2013 7:28 pm

        Is there any way then to not have the username password validator called on every single WCF request?

  12. January 7, 2014 8:23 am

    in the web.config file, do i need to set authentication mode to Forms or None

    • January 7, 2014 9:08 pm

      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.

  13. BBuff permalink
    February 8, 2014 4:49 pm

    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.

  14. fel0niousmonk permalink
    March 17, 2014 6:59 pm

    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.)

    • March 18, 2014 11:25 am

      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

      • fel0niousmonk permalink
        March 18, 2014 2:23 pm

        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.

        • March 18, 2014 2:27 pm

          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 :)

          • fel0niousmonk permalink
            March 19, 2014 6:27 pm

            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?

          • March 20, 2014 8:02 am

            <system.identityModel.services>
            <federationConfiguration>
            <cookieHandler requireSsl=”false” />

  15. KARTHIKEYAN N R permalink
    June 11, 2014 2:22 am

    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 …

    • June 11, 2014 8:35 am

      Perhaps you don’t have the SAM registered in the modules section of web.config?

  16. arif permalink
    July 3, 2014 7:33 am

    how can i read if the token cookies is already created ?

  17. arif permalink
    July 4, 2014 10:41 am

    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

    • July 4, 2014 4:42 pm

      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.

  18. arif permalink
    July 5, 2014 7:18 am

    Thanks for your response,
    but how do i know that the cookies still exist or not.

    Regards

  19. arif permalink
    July 5, 2014 9:24 am

    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();
    }

    • July 5, 2014 7:09 pm

      This sounds more like a WIF issue — I’d suggest using the Microsoft support forums or contacting Microsoft support directly.

  20. arif permalink
    July 6, 2014 3:56 am

    Ok.. thank you

  21. Kippie permalink
    July 18, 2014 3:45 am

    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?

    • July 18, 2014 9:06 am

      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?

  22. cotepatrice permalink
    February 19, 2015 3:53 pm

    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 ?

  23. Jason Coyne permalink
    April 17, 2015 11:46 am

    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?

    • April 20, 2015 10:40 am

      Yes, that’s just the persistent flag when you issue the cookie with the session authentication manager (SAM).

  24. aisha permalink
    February 18, 2016 9:02 pm

    Thank you. Thank you. Thankyou.

  25. March 21, 2016 6:44 am

    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

  26. Shahin permalink
    January 13, 2017 3:23 pm

    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.

Trackbacks

  1. SessionAuthenticationModule Cookie Handler not creating HttpOnly secure cookie | Technology & Programming
  2. » Session maintenance in cloud application ASP.Net MVC

Leave a Reply to brockallen Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: