Sunday 10 May 2015

Lock user after number of wrong attempts in MVC 5 using identity 2

First, create a new ASP.NET MVC 5 project and select the Individual User Accounts authentication. Then, install the ASP.NET Idenetity 2 framework using the package manager console.

Next, add the following default value settings in web.config

1
2
3
4
5
<appSettings>
  <add key="UserLockoutEnabledByDefault" value="true" />
  <add key="DefaultAccountLockoutTimeSpan" value="15" />
  <add key="MaxFailedAccessAttemptsBeforeLockout" value="3" />
</appSettings>
By setting UserLockoutEnabledByDefault to true, we are configuring the application to enforce globally that for every user that gets created he/she is subject to lockouts if the user reaches the maximum failed login attempts. Off course, if you do not want to enforce lockouts for particular users such as administrators you can do so when creating their accounts.
The value for DefaultAccountLockoutTimeSpan is in minutes so you can set to what every value you prefer. The example is set to 15 minutes which means if the user reaches the maximum failed login attempts, the user will have to wait for 15 minutes before being allowed to login.
The MaxFailedAccessAttemptsBeforeLockout setting is the number of login attempts you allow the user to retry before a lockout. The example allows 3 attempts after warning the user.
Next, open the IdentityConfig.cs file which is found in the App_Start folder and add the following in the Create method of the ApplicationUserManager class.

1
2
3
4
5
6
7
8
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
    // other code removed for brevity      
    manager.UserLockoutEnabledByDefault = Convert.ToBoolean(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"].ToString());
    manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(Double.Parse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
    manager.MaxFailedAccessAttemptsBeforeLockout = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString());
}
It is in the Create method that you configure the lockout settings by refering to the default values set in web.config above.
The final step is to perform the lockout checks and you do that in the POST login action method. So open Account controller and replace it with the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        // find user by username first
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user != null)
        {
            var validCredentials = await UserManager.FindAsync(model.Email, model.Password);
            // When a user is lockedout, this check is done to ensure that even if the credentials are valid
            // the user can not login until the lockout duration has passed
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                ModelState.AddModelError("", string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
            }
            // if user is subject to lockouts and the credentials are invalid
            // record the failure and check if user is lockedout and display message, otherwise,
            // display the number of attempts remaining before lockout
            else if (await UserManager.GetLockoutEnabledAsync(user.Id) && validCredentials == null)
            {
                // Record the failure which also may cause the user to be locked out
                await UserManager.AccessFailedAsync(user.Id);
                string message;
                if (await UserManager.IsLockedOutAsync(user.Id))
                {
                    message = string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString());
                }
                else
                {
                    int accessFailedCount = await UserManager.GetAccessFailedCountAsync(user.Id);
                    int attemptsLeft =
                        Convert.ToInt32(
                            ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString()) -
                        accessFailedCount;
                    message = string.Format(
                        "Invalid credentials. You have {0} more attempt(s) before your account gets locked out.", attemptsLeft);
                }
                ModelState.AddModelError("", message);
            }
            else if (validCredentials == null)
            {
                ModelState.AddModelError("", "Invalid credentials. Please try again.");
            }
            else
            {
                await SignInAsync(user, model.RememberMe);
                // When token is verified correctly, clear the access failed count used for lockout
                await UserManager.ResetAccessFailedCountAsync(user.Id);
                return RedirectToLocal(returnUrl);
            }
        }
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}
When a user attempts to login, the first check is to find out if the account is locked out and inform the user accordingly. Then it checks if the user is subject to lockouts and if it does and the credentials are invalid, record the failure(increment the access failed count) and inform the user that he/she has how many more login attempts before account gets locked out. If the account is not locked out and the credentials are valid, then sign the user in and reset the access failed count to zero.
The following shows the message a valid user gets when he/she fails to login the first time

The following shows the message a valid user gets when the account gets locked out

2 comments:

Saturday Offerday said...

Store does not implement IUserLockoutStore.

getting this error

Saturday Offerday said...

Store does not implement IUserLockoutStore.

getting this error

Post a Comment


                                                            
 
Design by Abhinav Ranjan Sinha