32

I have an application that used to use FormsAuthentication, and a while ago I switched it to use the IdentityModel from WindowsIdentityFramework so that I could benefit from claims based authentication, but it was rather ugly to use and implement. So now I'm looking at OwinAuthentication.

I'm looking at OwinAuthentication and the Asp.Net Identity framework. But the Asp.Net Identity framework's only implementation at the moment uses EntityModel and I'm using nHibernate. So for now I'm looking to try bypassing Asp.Net Identity and just use the Owin Authentication directly. I was finally able to get a working login using the tips from "How do I ignore the Identity Framework magic and just use the OWIN auth middleware to get the claims I seek?", but now my cookie holding the claims is rather large. When I used the IdentityModel I was able to use a server side caching mechanism that cached the claims on the server and the cookie just held a simple token for the cached information. Is there a similar feature in OwinAuthentication, or would I have to implement it myself?

I expect I'm going to be in one of these boats...

  1. The cookie stays as 3KB, oh well it's a little large.
  2. Enable a feature similar to IdentityModel's SessionCaching in Owin that I don't know about.
  3. Write my own implementation to cache the information causing the cookie to bloat and see if I can hook it up when I configure Owin at application startup.
  4. I'm doing this all wrong and there's an approach I've not thought of or I'm misusing something in Owin.

    public class OwinConfiguration
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Application",
                AuthenticationMode = AuthenticationMode.Active,
                CookieHttpOnly = true,
                CookieName = "Application",
                ExpireTimeSpan = TimeSpan.FromMinutes(30),
                LoginPath = "/Login",
                LogoutPath = "/Logout",
                ReturnUrlParameter="ReturnUrl",
                SlidingExpiration = true,
                Provider = new CookieAuthenticationProvider()
                {
                    OnValidateIdentity = async context =>
                    {
                        //handle custom caching here??
                    }
                }
                //CookieName = CookieAuthenticationDefaults.CookiePrefix + ExternalAuthentication.ExternalCookieName,
                //ExpireTimeSpan = TimeSpan.FromMinutes(5),
            });
        }
    }
    

UPDATE I was able to get the desired effect using the information Hongye provided and I came up with the below logic...

Provider = new CookieAuthenticationProvider()
{
    OnValidateIdentity = async context =>
    {
        var userId = context.Identity.GetUserId(); //Just a simple extension method to get the ID using identity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier) and account for possible NULLs
        if (userId == null) return;
        var cacheKey = "MyApplication_Claim_Roles_" + userId.ToString();
        var cachedClaims = System.Web.HttpContext.Current.Cache[cacheKey] as IEnumerable<Claim>;
        if (cachedClaims == null)
        {
            var securityService = DependencyResolver.Current.GetService<ISecurityService>(); //My own service to get the user's roles from the database
            cachedClaims = securityService.GetRoles(context.Identity.Name).Select(role => new Claim(ClaimTypes.Role, role.RoleName));
            System.Web.HttpContext.Current.Cache[cacheKey] = cachedClaims;
        }
        context.Identity.AddClaims(cachedClaims);
    }
}
Community
  • 1
  • 1
Nick Albrecht
  • 16,607
  • 10
  • 66
  • 101
  • Why don't you use a custom implementation of the ASP.NET Identity? There are already implementations on NuGet. – Caleb Kiage Dec 04 '13 at 05:57
  • There weren't any at the time when I was dealing with this, what ones are you referring to? – Nick Albrecht Dec 05 '13 at 23:41
  • Nhibernate.AspNet.Identity and also AspNet.Identity.NHibernate (I created this using SharpArchitecture and FluentNHibernate. It's a prerelease version though) – Caleb Kiage Dec 07 '13 at 07:59
  • 2
    There is a field on the CookieAuthenticationOptions object called "SessionStore" which is described as "An optional container in which to store the identity across requests. When used, only a session identifier is sent to the client. This can be used to mitigate potential problems with very large identities." This seems like what you are trying to do. Unfortunately, I couldn't find any reference about how to actually create one of these SessionStores. – ThisGuy Dec 20 '14 at 00:43
  • ***Claim*** is `System.Security.Claims.Claim` ? Code for `GetUserId` extension? – Kiquenet May 20 '16 at 07:58

3 Answers3

15

OWIN cookie authentication middleware doesn't support session caching like feature yet. #2 is not an options.

#3 is the right way to go. As Prabu suggested, you should do following in your code:

OnResponseSignIn:

  • Save context.Identity in cache with a unique key(GUID)
  • Create a new ClaimsIdentity embedded with the unique key
  • Replace context.Identity with the new identity

OnValidateIdentity:

  • Get the unique key claim from context.Identity
  • Get the cached identity by the unique key
  • Call context.ReplaceIdentity with the cached identity

I was going to suggest you to gzip the cookie, but I found that OWIN already did that in its TicketSerializer. Not an option for you.

Hongye Sun
  • 3,868
  • 1
  • 25
  • 18
  • The Claims that are causing the size of the cookie to inflate in my case are the roles used for permissions throughout the site. Is there a reason I should cache and replace the identity in it's entirety, or can I leave the identity intact and just cache and add the missing claims `ClaimTypes.Role` in the `OnValidateIdentity` task? – Nick Albrecht Oct 07 '13 at 18:52
  • Sure. You can definitely customize the code to meet your app's requirement. What I posted is a generic way to reference cookie from server cache. – Hongye Sun Oct 07 '13 at 19:18
  • full source code sample for `Create a new ClaimsIdentity embedded with the unique key` and `Replace context.Identity with the new identity` ? – Kiquenet May 20 '16 at 07:53
8
Provider = new CookieAuthenticationProvider()
{
    OnResponseSignIn = async context =>
    {
        // This is the last chance before the ClaimsIdentity get serialized into a cookie. 
        // You can modify the ClaimsIdentity here and create the mapping here. 
        // This event is invoked one time on sign in. 
    }, 
    OnValidateIdentity = async context => 
    {
        // This method gets invoked for every request after the cookie is converted 
        // into a ClaimsIdentity. Here you can look up your claims from the mapping table. 
    }
}
radbyx
  • 9,352
  • 21
  • 84
  • 127
Praburaj
  • 11,417
  • 1
  • 23
  • 20
  • 1
    I already knew about that section of code, and copy/pasting it doesn't answer my question. – Nick Albrecht Oct 07 '13 at 16:46
  • 1
    Check out the OnResponseSignIn event that I have shown above and the comment with in it. OnValidateIdentity as I have mentioned is invoked for every request. Essentially there are 2 points - OnResponseSignIn create the mapping, OnValidateIdentity - look up the claims. – Praburaj Oct 07 '13 at 17:31
1

You can implement IAuthenticationSessionStore to store cookies into database.

Here's example for storing cookie in redis.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SessionStore = new RedisSessionStore(new TicketDataFormat(dataProtector)),
LoginPath = new PathString("/Auth/LogOn"),
LogoutPath = new PathString("/Auth/LogOut"),

});

Check out full example at here

Alex Nguyen
  • 1,032
  • 12
  • 27