ASP.NET Core 3.1 - Login Lockout

Ken Haggerty
Created 02/20/2021 - Updated 02/23/2021 23:39

This article will describe the implementation of a configurable lockout pause after a configurable number of failed login attempts. I will assume you have downloaded the ASP.NET Core 3.1 - Users Without Identity Project or created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.

Users Without Identity Project and Article Series

UWIP v2 implements AccessFailedCount and LockoutEndDate properties for the AppUser. The AccessFailedCount is incremented for every identifiable failed login attempt.

Entities > AppUser:
[Display(Name = "Access Failed Count")]
public int AccessFailedCount { get; set; }
[Display(Name = "Lockout End")]
public DateTimeOffset? LockoutEndDate { get; set; }

The UserService implements maxFailedAccessAttempts = 5 and LockoutTimeSpan = TimeSpan. FromMinutes(15) private readonly properties.

Services > UserService:
private readonly int maxFailedAccessAttempts = 5;
private readonly TimeSpan lockoutTimeSpan = TimeSpan.FromMinutes(15);

The UserService implements a UpdateAppUserAccessFailedCountAsync function which is called after failed attempts on the Login, LoginWith2fa, and LoginWithRecoveryCode pages. If the failed access count is greater than or equal to the configured maximum number of attempts, the user will be locked out for the configured lockout time span.

Services > UserService:
public async Task<bool> UpdateAppUserAccessFailedCountAsync(int appUserId)
{
    var appUser = await TrackAppUserByIdAsync(appUserId).ConfigureAwait(false);
    if (appUser == null) return false;

    appUser.AccessFailedCount++;
    // If this meets the threshold for lockout, lock them out and reset the access failed count
    if (appUser.AccessFailedCount >= maxFailedAccessAttempts)
    {
        appUser.LockoutEndDate = DateTimeOffset.UtcNow.Add(lockoutTimeSpan);
        appUser.AccessFailedCount = 0;
    }
    return await UpdateAppUserAsync(appUser, appUser.RowVersion).ConfigureAwait(false);
}

The SignInService implements a PasswordSignInAsync function which returns a Microsoft. AspNetCore. Identity. SignInResult indicating the result of a sign in operation.

Services > SignInService:
public async Task<SignInResult> PasswordSignInAsync(string loginName, string password, bool isPersistent)
{
    var appUser = await _userService.GetAppUserByLoginNameAsync(loginName).ConfigureAwait(false);
    if (appUser == null) return SignInResult.Failed;
    if (!appUser.EmailConfirmed) return SignInResult.NotAllowed;
    if (appUser.LockoutEndDate > DateTimeOffset.UtcNow) return SignInResult.LockedOut;

    return await PasswordSignInAsync(appUser, password, isPersistent).ConfigureAwait(false);
}

If the SignInResult indicates LockedOut the user is redirected to the Locked Out page.

Locked Out
Locked Out Mobile
Update 02/23/2021

I updated the article links.

Article Tags:

Authorization EF Core Model

Comment Count = 0

Please log in to comment.

Login Register
Logged in users receive web notifications.
Web Notifications