ASP.NET Identity 2 Invalidate Identity Difficulties
Asked Answered
B

3

3

I have been updating my implementation of ASP.NET Identity all day today and I feel I'm on the last step, but just can't make it work. All I want to happen is to have the user's current session (if any) invalidated when something about them changes and to send them back to the login page. From the dozens of Identity related articles I've been reading today, I've settled that I have to override the OnValidateIdentity delegate, but it's just not working. Below is my code, I would really appreciate it if someone could tell me what I'm missing because I surely am not seeing it...

OwinConfiguration.cs

public static class OwinConfiguration {
    public static void Configuration(
        IAppBuilder app) {
        if (app == null) {
            return;
        }

        // SOLUTION: the line below is needed so that OWIN can
        // instance the UserManager<User, short>
        app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<UserManager<User, short>>());

        // SOLUTION: which is then used here to invalidate
        app.UseCookieAuthentication(new CookieAuthenticationOptions {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/"),
            ExpireTimeSpan = new TimeSpan(24, 0, 0),
            Provider = new CookieAuthenticationProvider {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager<User, short>, User, short>(
                    // SOLUTION: make sure this is set to 0 or it will take
                    // however long you've set it to before the session is
                    // invalidated which to me seems like a major security
                    // hole. I've seen examples set it to 30 minutes, in
                    // which time a disgruntled employee (say, after being
                    // fired) has plenty of opportunity to do damage in the
                    // system simply because their session wasn't expired
                    // even though they were disabled...
                    validateInterval: TimeSpan.FromMinutes(0),
                    regenerateIdentityCallback: (m, u) => u.GenerateUserIdentityAsync(m),
                    getUserIdCallback: (id) => short.Parse(id.GetUserId())
                )
            },
            SlidingExpiration = true
        });
    }
}

The GenerateUserIdentityAsync method looked like it needed to be a part of the entity, which I didn't like, so I made an extesion method for it that's internal to the assembly with the OWIN configuration:

UserExtensions.cs

internal static class UserExtensions {
    public static async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        this User user,
        UserManager<User, short> manager) {
        var userIdentity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

        return userIdentity;
    }
}

I have a feeling that it has something to do with insancing the UserManager<User, short>, but I can't seem to resolve it. I think the OWIN app has to create a singleton of it for the request, but it's not happening and thus the validation override isn't working? The thing is, I'm using Ninject, and I'm not sure how to make it cooperate with OWIN since OWIN is much earlier in the pipeline... Here's the Ninject configuration:

NinjectConfiguration.cs

namespace X.Dependencies {
    using System;
    using System.Linq;
    using System.Web;
    using Data;
    using Data.Models;
    using Identity;
    using Microsoft.AspNet.Identity;
    using Microsoft.Owin.Security;
    using Microsoft.Web.Infrastructure.DynamicModuleHelper;
    using Ninject;
    using Ninject.Modules;
    using Ninject.Web.Common;
    using Services;

    public static class NinjectConfiguration {
        private static readonly Bootstrapper Bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));

            Bootstrapper.Initialize(CreateKernel);
        }

        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop() {
            Bootstrapper.ShutDown();
        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel() {
            var kernel = new StandardKernel();

            try {
                kernel.Bind<Func<IKernel>>().ToMethod(
                    c => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);

                return kernel;
            } catch {
                kernel.Dispose();

                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(
            IKernel kernel) {
            if (kernel == null) {
                return;
            }

            kernel.Bind<XContext>().ToSelf().InRequestScope();

            kernel.Bind<IUserStore<User, short>>().To<UserStore>().InRequestScope();

            kernel.Bind<IAuthenticationManager>().ToMethod(
                c =>
                    HttpContext.Current.GetOwinContext().Authentication).InRequestScope();

            RegisterModules(kernel);
        }

        private static void RegisterModules(
            IKernel kernel) {
            var modules = AssemblyHelper.GetTypesInheriting<NinjectModule>().Select(Activator.CreateInstance).Cast<NinjectModule>();

            kernel.Load(modules);
        }
    }
}

A lot of the OWIN and Identity portions were put together by copy/pasting/adjusting from what I've found online... I would really appreciate some help. Thanks in advance!

Barracuda answered 1/12, 2015 at 6:25 Comment(0)
C
4

Most likely you are missing UserManager registration with OWIN.

The MVC template given with latest VS has these lines of code:

app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

This is run very early in the application lifecycle and effectively registers the delegate on how to create ApplicationUserManager. This code usually sits just before your line app.UseCookieAuthentication. And it is required to provide OWIN with delegate on how to create ApplicationUserManager because it is used in the routine of cookie invalidation when SecurityStamp is changed in the database.

Now the tricky part is to give OWIN correct delegate to work with. A lot of the times your DI container is created after this code is run. So you need to be careful about this. And usually you need to register your DI as a ServiceProvider for MVC to get your controllers resolved. And if this works, you'll get your ApplicationUserManager from MVC service provider:

app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<ApplicationUserManager>());

Here is the full sample of the code. Or you keep the static method that creates an instance of the ApplicationUserManager.

I've blogged about using DI with Identity. And there is a GitHub repository with working code sample of DI container working with Indentity. I hope this can give you some ideas.

Cidevant answered 1/12, 2015 at 9:20 Comment(3)
Ok, so the solution came from two things. First, as you said, I needed this: app.CreatePerOwinContext(() => DependencyResolver.Current.GetService<UserManager<User, short>>());. Second, I needed to change the validateInterval to TimeSpan.FromMinutes(0) so as to immediately check for invalidation. Before it was set to 30 minutes, so even though the user was invalidated as far as the SecurityStamp, their session was not being expired. Now, it finally works, so I can finally move on to something more productive. Thanks for the help!Barracuda
@Alex I would not recommend to use zero-minutes as a checking interval - this means on every request the database is hit with a request for a user table. Also the auth cookie is always changing - additional load on the application to encrypt the cookie. I usually settle on 2-5 minutes to avoid extra load on your application, but to have a soon-enough cookie invalidation.Cidevant
@Alex, I probably did not understand your question.Hardihood
H
0

Your cookie is set to expire in 30 minutes but with SlidingExpiration set to true. What it means is, the cookie will expire in 30 minutes if a user has not used the web site for more than 30 minutes. But if a user has stop using the web site for 28 mins and then comes to click on a button at the 29 mins or within the 30 minute expiration duration, the cookie expiration will be reset to another 30 mins. In effect that cookie will not expire. And you will not see the effect of the cookie expiration. The cookie will only be invalid after 30 minutes and session will no longer be valid. The session is valid if the cookie is valid and vice versa.

This may be what you are experiencing.

Set to false.

 SlidingExpiration = false
Hardihood answered 1/12, 2015 at 6:53 Comment(6)
That's not correct. 30 minutes in the given sample is how often the cookie is compared to the values in the database. Cookie expiration is set to 24 hours.Cidevant
Sorry just looked again. It is 24 hours. But my explaination remains the same. The session is valid as long as the cookie has not expired. And my explanation for SlidingExpiration is the same- the expiration will always be reset any time there is user activity on the web page. So the cookie never expires and the session never expires. ASP.NET MVC uses cookies for authenticationHardihood
In this case it is probably best to update your answer (which is indeed correct in the essence) and get upvotes? -)Cidevant
Sorry just looked again. It is 24 hours. And my explanation for SlidingExpiration is the same- the expiration will always be reset any time there is user activity on the web page. So the cookie never expires and the session never expires. ASP.NET MVC uses cookies for authentication. Please read documentation on Sliding Expiration and cookies, typing on a small screen here.Hardihood
@coderealm, I don't think you're correct because the cookie contains a copy of the SecurityStamp in it. Somewhere in the OWIN pipeline that get's checked and compared against what's in the database. If it's identical, then the session continues, get's its expiration extended and life goes on. If it's not identical is when the session gets invalidated, the cookie is expired and the user is redirected to log back in. The sliding expiration has no effect on that. There's nothing for it to slide when the cookie was forced to expire prior to that. I just can't get the invalidation to occur...Barracuda
@Alex, you can test these things. Change the expiration time, to very less time than 24 hours, say 2 min. The important thing, is the Action Method should have [Authorise] attribute declaration, this means you are required to login to use that web page. Set Sliding Expiration to false. Build, start the app, login. The cookie is created at this stage with expiration of 2min and sliding expiration of false. In the 3rd min try and access a web page that has its action method with Authorise attribute, you will be redirected to the Login page.Hardihood
H
0

Some code will explain better. This method is only accessible after you have logged in

[Authorise]
public ActionResult Dashboard()
{
   return View();
}

Your authentication cookie settings

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                validateInterval: TimeSpan.FromMinutes(2),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        },
        SlidingExpiration = false                
    });  
Hardihood answered 1/12, 2015 at 19:50 Comment(2)
@Alex, I am not sure I understood your question.Hardihood
I was having a hard time with invalidating existing sessions. The problem ended up being that the UserManager was not being instanced for the SecurityStampValidator to use it. One other "minor" issue was that the validateInterval was set too high.Barracuda

© 2022 - 2024 — McMap. All rights reserved.