Wednesday, April 29, 2015

ExpireTimeSpan ignored after regenerateIdentity / validateInterval duration in MVC Identity (2.0.1)

Been scratching my head all day on this one. I'm trying to set up "very long" login sessions in MVC Identity 2.0.1. (30 days).
I use the following cookie startup:
      app.UseCookieAuthentication(new CookieAuthenticationOptions
        {

            SlidingExpiration = true,
            ExpireTimeSpan = System.TimeSpan.FromDays(30),
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/My/Login"),
            CookieName = "MyLoginCookie",
            Provider = new CookieAuthenticationProvider
            {                           
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),

                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });
Which on the whole, works fine. The cookie is set 30 days hence, all looks good.
If I close browser and come back after "validateInterval" duration has passed (30mins here) I'm still logged in, however the cookie is now re-issued as "session" only (correct cookie name still)! The 30 day expiration is gone.
If I now close browser/reopen again I'm no longer logged in.
I have tested removing the "Provider" and all works as expected then, I can come back several hours later and I'm still logged in fine. I read that it is best practice to use the stamp revalidation though, so am unsure how to proceed.
Cheers!
shareimprove this question
   
Still a bug in 2.1.0 –  QuantumHive Mar 9 at 10:27

2 Answers

up vote15down voteaccepted
When the SecurityStampValidator fires the regenerateIdentity callback, the currently authenticated user gets re-signed in with a non-persistent login. This is hard-coded, and I don't believe there is any way to directly control it. As such, the login session will continue only to the end of the browser session you are running at the point the identity is regenerated.
Here is an approach to make the login persistent, even across identity regeneration operations. This description is based on using Visual Studio MVC ASP.NET web project templates.
First we need to have a way to track the fact that a login session is persistent across separate HTTP requests. This can be done by adding an "IsPersistent" claim to the user's identity. The following extension methods show a way to do this.
public static class ClaimsIdentityExtensions
{
    private const string PersistentLoginClaimType = "PersistentLogin";

    public static bool GetIsPersistent(this System.Security.Claims.ClaimsIdentity identity)
    {
        return identity.Claims.FirstOrDefault(c => c.Type == PersistentLoginClaimType) != null;
    }

    public static void SetIsPersistent(this System.Security.Claims.ClaimsIdentity identity, bool isPersistent)
    {
        var claim = identity.Claims.FirstOrDefault(c => c.Type == PersistentLoginClaimType);
        if (isPersistent)
        {
            if (claim == null)
            {
                identity.AddClaim(new System.Security.Claims.Claim(PersistentLoginClaimType, Boolean.TrueString));
            }
        }
        else if (claim != null)
        {
            identity.RemoveClaim(claim);
        }
    }
}
Next we need to make the "IsPersistent" claim when the user signs in requesting a persistent session. For example, your ApplicationUser class may have a GenerateUserIdentityAsyncmethod which can be updated to take an isPersistent flag parameter as follows to make such a claim when needed:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, bool isPersistent)
{
    var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
    userIdentity.SetIsPersistent(isPersistent);
    return userIdentity;
}
Any callers of ApplicationUser.GenerateUserIdentityAsync will now need to pass in the isPersistent flag. For example, the call to GenerateUserIdentityAsync in AccountController.SignInAsync would change from
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, 
    await user.GenerateUserIdentityAsync(UserManager));
to
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent },
    await user.GenerateUserIdentityAsync(UserManager, isPersistent));
Lastly, the CookieAuthenticationProvider.OnValidateIdentity delegate used in the Startup.ConfigureAuth method needs some attention to preserve the persistence details across identity regeneration operations. The default delegate looks like:
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
    validateInterval: TimeSpan.FromMinutes(20),
    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
This can be changed to:
OnValidateIdentity = async (context) =>
{
    await SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
        validateInterval: TimeSpan.FromMinutes(20),
        // Note that if identity is regenerated in the same HTTP request as a logoff attempt,
        // the logoff attempt will have no effect and the user will remain logged in.
        // See https://aspnetidentity.codeplex.com/workitem/1962
        regenerateIdentity: (manager, user) =>
            user.GenerateUserIdentityAsync(manager, context.Identity.GetIsPersistent())
    )(context);

    // If identity was regenerated by the stamp validator,
    // AuthenticationResponseGrant.Properties.IsPersistent will default to false, leading
    // to a non-persistent login session. If the validated identity made a claim of being
    // persistent, set the IsPersistent flag to true so the application cookie won't expire
    // at the end of the browser session.
    var newResponseGrant = context.OwinContext.Authentication.AuthenticationResponseGrant;
    if (newResponseGrant != null)
    {
        newResponseGrant.Properties.IsPersistent = context.Identity.GetIsPersistent();
    }
}
shareimprove this answer
   
Wow, thanks for the thorough response! I will give this a try :) –  GregTheDev Jun 22 '14 at 14:50
1 
Does anyone know if Microsoft plan to address this?] –  Matt Roberts Mar 6 at 17:58
   
Wow, I was gonna ask a question about this, because I had the same symptoms as @GregTheDev. But this worked out for me. –  QuantumHive Mar 9 at 10:25

Sunday, April 19, 2015

Explicitly Controlling Cache Dependencies in MVC

Explicitly Controlling Cache Dependencies in MVC

In the past, I have always used the OutputCacheAttribute  when I wanted to cache the result of an Action in my MVC application; it’s simple, and it gets basic caching up and running with very little effort.
Unfortunately “simple and quick” are not quite as useful when you need a little more control…

Scenario

Let’s say you have a resource that you automatically generate in your controller, but that creating that resource takes a long time…
?
1
2
3
4
5
6
public async Task<ActionResult> Slow()
{
    var resource = await _service.GenerateResource();
    // ...5 minutes later...
    return View(resource);
}
Understandably, you want to cache the result of this action so that subsequent visitors don’t all have to wait around for the resource to be created.  [OutputCache] to the rescue, right?

Not So Fast!

Unfortuantely, the resource is not entirely constant – it can be altered by certain user actions in other areas of the application, and when that happens it will need to be regenerated.
[OutputCache] will allow us to vary the cached result by a number of variables (host, action arguments, custom strings…) and supports timeouts etc, but it does not allow another piece of code somewhere in the application to say “that resource is not longer valid”.

An Alternative to [OutputCache]

One alternative means of caching the results of an action is to call theAddCacheDependency method on the Response object:
1
2
3
4
5
6
public async Task<ActionResult> Index()
{
    Response.AddCacheDependency(new CacheDependency(...));
             
    //...
}
The CacheDependency instance in this example is an ASP.NET class that is used by the caching framework to determine when the resource we created has been changed.
The base CacheDependency implementation allows you to specify one or more file system paths that will be monitored, and will invalidate itself when any of those files is updated.  There is also a SqlCacheDependency class that observes the results of a SQL query and invalidates when they change.
Neither of these implementations will give us quite what we are looking for –  the ability to invoke a method from anywhere within the codebase that explicitly marks the cached content as changed – but they are leading us in the right direction.
If we can create a CacheDependency instance that is uniquely identifiable for the slow resource then we can add it to the response and force it to invalidate itself at a later date.

Extending CacheDependency

Before we can get into how we add our CacheDependency instance, we need to create an implementation that will allow us to explicitly invalidate it through code.  Thankfully,CacheDependency exposes a number of methods to it’s inheriting classes that mean we can achieve our explicit invalidate very easily.
The minimum that we need to do to make the CacheDependency work is to pass a list of paths into the base constructor and to provide a valid value from the GetUniqueIDmethod.  We know that we do not want to depend on any file system resources so we can just pass an empty list into the constructor, and as we need a unique ID anyway (to identify the cached resource later) we can just pass this into the constructor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ExplicitCacheDependency : CacheDependency
{
    private string _uniqueId;
 
    public ExplicitCacheDependency(string uniqueId)
        : base(new string[0]) //no file system dependencies
    {
        _uniqueId = uniqueId;
    }
         
    public override string GetUniqueID()
    {
        return _uniqueId;
    }
}
CacheDependency has a protected NotifyDependencyChanged method that will notify the caching framework that the cached item is no longer valid. In most implementations this would be invoked in some callback, but for our purposes we can just add a newInvalidate method and invoke it directly:
1
2
3
4
public void Invalidate()
{
    base.NotifyDependencyChanged(this, EventArgs.Empty);
}
Voila – a cache dependency that we can explicitly invalidate.  But how can we get a reference to this in the various places that we need it?

CacheDependencyManager

Creating a new cache dependency doesn’t help us much if we can’t get a reference to it later – otherwise, how can we call Invalidate?  Let’s create a new class that handles the creation and invalidation of the new cache dependencies: CacheDependencyManager.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CacheDependencyManager
{
    private Dictionary<string, ExplicitCacheDependency> _dependencies
        = new Dictionary<string, ExplicitCacheDependency>();
 
    public CacheDependency GetCacheDependency(string key)
    {
        if (!_dependencies.ContainsKey(key))
            _dependencies.Add(key, new ExplicitCacheDependency(key));
 
        return _dependencies[key];
    }
 
    public void InvalidateDependency(string key)
    {
        if (_dependencies.ContainsKey(key))
        {
            var dependency = _dependencies[key];
            dependency.Invalidate();
            dependency.Dispose();
            _dependencies.Remove(key);
        }
    }
}
Note: in the interests of brevity I have not included the thread-safe version here; this is aterrible idea in the real world, so make sure you include some appropriate locking!
This CacheDependencyManager is intended to hide the detail of how the dependency instances are created and invalidated from calling classes, which it achieves through 2 public methods:
  • GetCacheDependency that creates a new ExplicitCacheDependency if none exists for the specified key, or returns the cached one if it has been previously created
  • InvalidateDependency that attempts to locate an existing cache dependency for the specified key, and invalidates and removes it if one is found.  If one doesn’t exist then it does nothing, so callers don’t need to know whether or not the resource has already been cached
One quick singleton pattern later and we can call these methods from throughout our codebase. When we invoke our slow action for the first time we need to add the dependency to the response using a unique key to identify the slow resource:
1
2
Response.AddCacheDependency(
        CacheDependencyManager.Instance.GetCacheDependency("ResourceKey"));
And how do we invalidate the resource after some arbitrary user action? We can just pass that same key into the Invalidate:
1
2
3
4
5
6
7
8
public class SomeOtherController : Controller
{
    public ActionResult SomeRandomUserAction()
    {
        CacheDependencyManager.Instance.Invalidate("ResourceKey")
        //...
    }
}
Obviously you could (should!) use dependency injection to pass theCacheDependencyManager to your controllers instead of accessing it directly, but the singleton will suffice for this example.

That’s All Folks

That’s the lot – now we can manually invalidate our slow resource whenever and from wherever we want, and our users can enjoy speedy cached resources the rest of the time!