Support us .Net Basics C# SQL ASP.NET Aarvi MVC Slides C# Programs Subscribe Download

ASP.NET Core account lockout

Suggested Videos
Part 120 - ASP.NET Core encryption and decryption example | Text | Slides
Part 121 - Change password in asp.net core | Text | Slides
Part 122 - Add password to local account linked to external login | Text | Slides

In this video we will discuss how to implement user account lockout in asp.net core.

What is account lockout
Account lockout is locking (i.e disabling) the account after too many failed logon attempts. Most banks lock the account after 5 failed attempts. After how many failed attempts, should the account be locked out, depends on the lockout policy of the company. The number of failed attempts after which an account should be locked is configurable in asp.net core.


Why should we lock accounts
Account lockout is to prevent attackers from brute-force attempts to guess a user's password. After certain number of failed logon attempts the account will be temporarily locked for a specified amount of time. How long the account should be locked is again configurable and we will see this in action in just a bit. 


Let's say we lock the account for 15 minutes after 5 failed logon attempts. After 15 minutes the user will get another 5 attempts to logon. After 5 failed attempts the account will be locked for another 15 minutes. So this means, it will take many years for an attacker to successfully crack the password. 

An organisation may also have password change policy, meaning the password must be changed every 1 or 2 months. So account lockout policy combined with password change policy makes it extremely difficult for an attacker to brute-force (i.e guess) password and gain access.

Configure account lockout options in asp.net core

Account lockout options are configured in ConfigureServices() method of the Startup class.

MaxFailedAccessAttempts - Specifies the number of failed logon attempts allowed before the account is locked out. The default is 5.
DefaultLockoutTimeSpan - Specifies the amount of the time the account should be locked. The default it 5 minutes.

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>(options =>
    {
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    });

    // Rest of the code
}

Enable account lockout

Modify the code in Login() action in AccountController to enable account lockout. The code is commented where required.

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
{
    model.ExternalLogins =
        (await signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid)
    {
        var user = await userManager.FindByEmailAsync(model.Email);

        if (user != null && !user.EmailConfirmed &&
            (await userManager.CheckPasswordAsync(user, model.Password)))
        {
            ModelState.AddModelError(string.Empty, "Email not confirmed yet");
            return View(model);
        }

        // The last boolean parameter lockoutOnFailure indicates if the account
        // should be locked on failed logon attempt. On every failed logon
        // attempt AccessFailedCount column value in AspNetUsers table is
        // incremented by 1. When the AccessFailedCount reaches the configured
        // MaxFailedAccessAttempts which in our case is 5, the account will be
        // locked and LockoutEnd column is populated. After the account is
        // lockedout, even if we provide the correct username and password,
        // PasswordSignInAsync() method returns Lockedout result and the login
        // will not be allowed for the duration the account is locked.
        var result = await signInManager.PasswordSignInAsync(model.Email,
                                        model.Password, model.RememberMe, true);

        if (result.Succeeded)
        {
            if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("index", "home");
            }
        }

        // If account is lockedout send the use to AccountLocked view
        if (result.IsLockedOut)
        {
            return View("AccountLocked");
        }

        ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
    }

    return View(model);
}

Account Locked View

After the account is locked, there are 2 options. Wait for the account lockout time to expire which in our case is 15 minutes and then try again or request password reset if you have forgotten the password.

<h3 class="text-danger">
    Your account is locked, please try again after sometime or you may
    <a asp-action="ForgotPassword" asp-controller="Account">
        reset your password by clicking here
    </a>
</h3>

Set lockout end date on successful password reset

If the user is lockedout, password reset can be requested. Upon successful password reset, set the account lockout end date to current UTC date time, so the user can login with the new password. Use SetLockoutEndDateAsync() method of the UserManager service for this.

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await userManager.FindByEmailAsync(model.Email);

        if (user != null)
        {
            var result =
                await userManager.ResetPasswordAsync(user, model.Token, model.Password);
            if (result.Succeeded)
            {
                // Upon successful password reset and if the account is lockedout, set
                // the account lockout end date to current UTC date time, so the user
                // can login with the new password
                if (await userManager.IsLockedOutAsync(user))
                {
                    await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow);
                }
                return View("ResetPasswordConfirmation");
            }

            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error.Description);
            }
            return View(model);
        }

        return View("ResetPasswordConfirmation");
    }
    return View(model);
}

asp.net core tutorial for beginners

1 comment:

  1. These are missing from this text:
    [HttpGet]
    [AllowAnonymous]
    public IActionResult ForgotPassword()
    {
    return View();
    }
    ----
    ResetPasswordViewModel

    ResetPassword.cshtml
    &
    ResetPasswordConfirmation.cshtml

    ReplyDelete

It would be great if you can help share these free resources