This project is read-only.

Indentity 2.1 - Lockout is not occuring

Sep 25, 2014 at 2:20 PM
Hi,

I've been configuring and re-configuring, but lockout is not being triggered for Identity 2.1. When shouldLockout is true, the "AccessFailedCount" is incremented and after three attempts the LockoutDateUtc is set, but "LockoutEnabled" is not flagged. I'm not sure why sample code has setting shoulLockout as false, since it doesn't increment "AccessFailedCount".

var manager = new ApplicationUserManager( new UserStore<ApplicationUser>( new ApplicationDbContext() ) )
        {
            // when new user is created, they will be "lockable" based on setting. 
            UserLockoutEnabledByDefault = true,
            DefaultAccountLockoutTimeSpan = 10,
            MaxFailedAccessAttemptsBeforeLockout = 3
        };
_userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();

_signInManager = new ApplicationSignInManager( m_userManager, HttpContext.Current.GetOwinContext().Authentication );

var status = _signInManager.PasswordSignIn( request.UserName, request.Password, request.RememberMe,
                shouldLockout: true);
Your help would be greatly appreciated!
Sep 25, 2014 at 3:44 PM
Also, I can set lockout based on LockoutEndDateUtc, but my understanding was signInmanager was supposed to handle it.
Sep 28, 2014 at 2:39 PM
Edited Sep 28, 2014 at 3:47 PM
Edit: Please ignore the post below, not had my coffee yet. Leaving it here incase it helps anyone else.

Problem line is here.
    DefaultAccountLockoutTimeSpan = 10,
You are setting the account lockout value to 10 'ticks', i.e. 10 milliseconds.

Try setting it to two hours.
DefaultAccountLockoutTimeSpan = new TimeSpan(2, 0, 0);
Other things to check.
How you are you seeding your users? I was pushing them in from XML and didn't populate the SecurityStamp field. Ensure this is setup correctly and use a user you have created using the identity system.

I migrated from using forms security and had not updated my web. config. Relevant bits should look like this.

<system.web>
    <authentication mode="None" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthenticationModule" />
    </modules>
  </system.webServer>

This caught me out too until I looked at the source code.

You need to ensure the MaxFailedAccessAttemptsBeforeLockout is set to a value other than 0 otherwise the Access failed count will be set to 0 by default.

If it is set to 0 then it will lock the account out by setting the lockout end date to some value and reset the access failed out to 0. even on the first failed attempt.

Hopefully you are using IOC, if so then ensure your registration looks something like the following.
container.Register<UserManager<User, int>, UserManager<User, int>>();
container.RegisterInitializer<UserManager<User, int>>(x => x.MaxFailedAccessAttemptsBeforeLockout = 5);
To understand what's going on under the hood take a look at the following method.
      public virtual async Task<IdentityResult> AccessFailedAsync(TKey userId)
        {
            var store = GetUserLockoutStore();
            var user = await FindByIdAsync(userId).WithCurrentCulture();

            // If this puts the user over the threshold for lockout, lock them out and reset the access failed count
            var count = await store.IncrementAccessFailedCountAsync(user).WithCurrentCulture();
            if (count >= MaxFailedAccessAttemptsBeforeLockout)
            {
                await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan));
                await store.ResetAccessFailedCountAsync(user).WithCurrentCulture();
            }
            return await UpdateAsync(user).WithCurrentCulture();
        }
As an asside, please can the team move over to using IOC in the examples as opposed to poor mans injection using owin call backs and those ugly management classes. Spent too much time refactorring the examples to get them working in production style code.

Cheers

Steve
Oct 3, 2014 at 8:56 PM
Hello Everyone,

We have an application using a old membership database that we have migrated to the new format (Identity ASP.NET 2.1) using the instructions detailed here http://www.asp.net/identity/overview/migrations/migrating-an-existing-website-from-sql-membership-to-aspnet-identity. Login process its working OK but We could not get the "AccessFailedCount" field be increased.

Note: We are using Visual Studio 2013 Web Project template to migrate existing code.


Any help is welcome. Thanks in advance
Oct 3, 2014 at 9:24 PM
Edited Oct 3, 2014 at 9:26 PM
Hi Alvaritus,
  1. Make sure you have the following configured in UserManager( using UserManager class as in the url):
public class UserManager : UserManager<User>

__//this is a mock up of what I believe you have__
public static UserManager Create(
                 IdentityFactoryOptions<UserManager> options,
                 IOwinContext context
            )
        {
UserLockoutEnabledByDefault = true,
DefaultAccountLockoutTimeSpan = new TimeSpan(2, 0, 0)
MaxFailedAccessAttemptsBeforeLockout = 3
};
  1. At the sign in logic, set shouldLockout to true
var status = signInManager.PasswordSignIn( userName, password, rememberMe,
                    __shouldLockout: true__);
Hope this helps
Oct 3, 2014 at 10:49 PM
Edited Oct 4, 2014 at 11:53 PM
Softling76 : Thanks for your answer!

The model that comes in the default web project in Visual Studio 2013 update 3 already contains these the settings that you mentioned. :(

I paste a summary of my code below :

public partial class Startup {
    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(CustomApplicationDbContext.Create);
        app.CreatePerOwinContext<CustomUserManager>(CustomUserManager.Create);
        app.CreatePerOwinContext<CustomApplicationSignInManager>(CustomApplicationSignInManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<CustomUserManager, CustomUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

public class CustomUserManager : UserManager<CustomUser>
{
    public CustomUserManager()
        : base(new UserStore<CustomUser>(CustomApplicationDbContext.Create()))
    {
        this.PasswordHasher = new SQLPasswordHasher();
    }

    public CustomUserManager(UserStore<CustomUser> userStore)
        : base(userStore)
    {
        this.PasswordHasher = new SQLPasswordHasher();
    }

    public static CustomUserManager Create(IdentityFactoryOptions<CustomUserManager> options, IOwinContext context)
    {
        var manager = new CustomUserManager(new UserStore<CustomUser>(context.Get<CustomApplicationDbContext>()));


        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<CustomUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
        // You can write your own provider and plug it in here.
        manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<CustomUser>
        {
            MessageFormat = "Your security code is {0}"
        });
        manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<CustomUser>
        {
            Subject = "Security Code",
            BodyFormat = "Your security code is {0}"
        });

        __// Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;__

        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = new DataProtectorTokenProvider<CustomUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
    }

public class CustomApplicationSignInManager : SignInManager<CustomUser, string>
{
    public CustomApplicationSignInManager(CustomUserManager userManager, IAuthenticationManager authenticationManager) :
        base(userManager, authenticationManager) { }

    public override Task<ClaimsIdentity> CreateUserIdentityAsync(CustomUser user)
    {
        return user.GenerateUserIdentityAsync((CustomUserManager)UserManager);
    }

    public static CustomApplicationSignInManager Create(IdentityFactoryOptions<CustomApplicationSignInManager> options, IOwinContext context)
    {
        return new CustomApplicationSignInManager(context.GetUserManager<CustomUserManager>(), context.Authentication);
    }
}

Login.ASPX

protected void LoginButton_Click(object sender, EventArgs e)
     {
        if (IsValid)
        {
            // Validate the user password
            var manager = Context.GetOwinContext().GetUserManager<CustomUserManager>();
            var signinManager = Context.GetOwinContext().GetUserManager<CustomApplicationSignInManager>();



            // This doen't count login failures towards account lockout
            // To enable password failures to trigger lockout, change to shouldLockout: true
            __var result = signinManager.PasswordSignIn(UserName.Text, Password.Text, false, shouldLockout: true);__
            var user = manager.FindByName(UserName.Text);
            switch (result)
            {
                case SignInStatus.Success:
                    //IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
                    if ( user != null)
                    {
                        manager.ResetAccessFailedCountAsync(user.Id);    
                    }

                     Response.Redirect("~/Default.aspx",false);
                     Context.ApplicationInstance.CompleteRequest();
                    break;
                case SignInStatus.LockedOut:
                    Response.Redirect("../Account/Lockout");
                    break;
                case SignInStatus.RequiresVerification:
                    Response.Redirect(String.Format("../Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}",
                                                    Request.QueryString["ReturnUrl"],
                                                    false),
                                      true);
                    break;
                case SignInStatus.Failure:
                default:
                    FailureText.Text = "No pudo ingresar al sitio, por favor verifique la información ingresada";
                    //if (user != null)
                    //{
                    //    if (manager.SupportsUserLockout && manager.GetLockoutEnabled(user.Id))
                    //    {
                    //        manager.AccessFailed(user.Id);
                    //    }
                    //    manager.AccessFailed(user.Id); 
                    //}
                    break;
            }
        }
    }
Oct 7, 2014 at 1:45 AM
I let my solution to the problem, maybe if it helps someone.

The method called PasswordSignIn, performs e-mail validation. The class called "CustomUser" that inherits from the base class "IdentityUser" has a small error (described in the article that I mentioned before --- http://www.asp.net/identity/overview/migrations/migrating-an-existing-website-from-sql-membership-to-aspnet-identity)

The class implements a Email property that its base class already has, So mail validator fails (null reference). To solve the problem, you must remove the Email property of the derived class "CustomUser"