ASP.NET Core  

Account Lockout After Failed Login Attempts | ASP.NET Core, Angular, and SQL Server

Account lockout is an important security feature that protects user accounts from brute-force attacks and repeated unauthorized login attempts. Without account lockout, an attacker could try thousands of password combinations until they eventually guess the correct one.

Most modern systems (Google, Microsoft, GitHub, banking apps) use account lockout to:

  • Slow down attackers

  • Protect user accounts

  • Prevent automated login attempts

  • Reduce password-guessing risks

In this article, you will learn how to build a complete Account Lockout System using:

  • ASP.NET Core Web API

  • Angular

  • SQL Server

  • Login attempt tracking

  • Lockout duration and reset logic

  • Secure protections against brute-force attacks

This article uses simple language and includes complete practical code for beginners.

What You Will Build

By the end of this tutorial, you will implement:

  1. Tracking failed login attempts in the database

  2. Automatic lockout after a certain number of failed attempts

  3. Auto unlock after a specified time (e.g., 5 or 15 minutes)

  4. Error messages guiding the user

  5. Angular UI updates

  6. Backend checks to enforce lockout

  7. Optional features like permanent lockout or admin unlock

Why Account Lockout Matters

Attackers use automated tools to attempt thousands of password guesses per minute. Without account lockout, they could easily breach weak or reused passwords.

Account lockout helps by:

  • Limiting the number of attempts

  • Slowing down attackers

  • Preventing automated scripts

  • Notifying users when suspicious activity happens

It is a proven layer of security and is recommended in OWASP and NIST guidelines.

System Architecture Overview

The system will use these fields in the User table:

  • FailedLoginAttempts

  • LockoutEnd

  • IsLockedOut (Optional boolean flag)

Flow

  1. User enters email and password

  2. Backend checks if the account is locked

  3. If locked, deny login

  4. If credentials are wrong, increase FailedLoginAttempts

  5. If attempts reach the limit, set lockout time (example: 5 minutes)

  6. If credentials are correct, reset FailedLoginAttempts

  7. Allow login

This protects your system from brute-force attacks.

Part 1: Backend Implementation (ASP.NET Core)

Step 1: Update User Model

File: Models/User.cs

public class User
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }

    public int FailedLoginAttempts { get; set; } = 0;
    public DateTime? LockoutEnd { get; set; }
}

Step 2: Account Lockout Configuration

Define your lockout policy:

public class LockoutSettings
{
    public const int MaxFailedAttempts = 5;  
    public const int LockoutMinutes = 5;    
}

Step 3: Modify Login Endpoint

File: AuthController.cs

[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto request)
{
    var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
    if (user == null)
        return BadRequest(new { Message = "Invalid email or password" });

    // Check if locked
    if (user.LockoutEnd.HasValue && user.LockoutEnd.Value > DateTime.Now)
    {
        var remaining = user.LockoutEnd.Value - DateTime.Now;

        return BadRequest(new 
        { 
            Message = $"Account locked. Try again in {remaining.Minutes} minutes and {remaining.Seconds} seconds." 
        });
    }

    // Check password
    if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
    {
        user.FailedLoginAttempts++;

        if (user.FailedLoginAttempts >= LockoutSettings.MaxFailedAttempts)
        {
            user.LockoutEnd = DateTime.Now.AddMinutes(LockoutSettings.LockoutMinutes);
            await _context.SaveChangesAsync();

            return BadRequest(new 
            { 
                Message = $"Account locked due to many failed attempts. Try again after {LockoutSettings.LockoutMinutes} minutes." 
            });
        }

        await _context.SaveChangesAsync();

        return BadRequest(new 
        { 
            Message = $"Invalid password. Attempts remaining: {LockoutSettings.MaxFailedAttempts - user.FailedLoginAttempts}" 
        });
    }

    // Successful login: reset attempts
    user.FailedLoginAttempts = 0;
    user.LockoutEnd = null;
    await _context.SaveChangesAsync();

    var token = _jwtService.GenerateToken(user);

    return Ok(new { Token = token });
}

What this code does

  • Checks if the account is currently locked

  • Locks the user if failed attempts reach the limit

  • Shows remaining attempts

  • Unlocks automatically after time expires

  • Resets attempts on successful login

Part 2: SQL Table Update

Update your Users table by adding the new fields:

ALTER TABLE Users 
ADD FailedLoginAttempts INT DEFAULT 0;

ALTER TABLE Users 
ADD LockoutEnd DATETIME NULL;

Part 3: Frontend Implementation (Angular)

Step 1: Modify Login Component

login.component.ts

submit() {
  this.auth.login(this.model).subscribe({
    next: res => {
      this.auth.saveToken(res.token);
      this.router.navigate(['/dashboard']);
    },
    error: err => {
      this.message = err.error.message;
    }
  });
}

Step 2: Display Messages in Template

login.component.html

<h2>Login</h2>

<input type="text" [(ngModel)]="model.email" placeholder="Email" />
<input type="password" [(ngModel)]="model.password" placeholder="Password" />

<button (click)="submit()">Login</button>

<p style="color: red">{{ message }}</p>

Angular will now show:

  • Wrong password attempts

  • Remaining attempts

  • Account locked message

Part 4: How the Lockout System Works

Example policy

  • 5 failed attempts allowed

  • After 5 wrong attempts → lock for 5 minutes

  • After 5 minutes → user can login again

  • On correct login → attempts reset to zero

Example scenario

User enters wrong password:

  • Attempt 1 → remaining 4

  • Attempt 2 → remaining 3

  • Attempt 3 → remaining 2

  • Attempt 4 → remaining 1

  • Attempt 5 → account locked for 5 minutes

After lockout ends:

  • User can try again

  • If successful, the counter resets

Part 5: Additional Security Improvements

You can enhance the system by adding:

1. Permanent Lockout After Multiple Lockouts

If user triggers 3 lockouts → Permanently lock until admin resets.

2. Email Notification on Lockout

Send alert:
"Your account was locked due to suspicious activity."

3. IP-Based Lockout

Prevent brute-force attacks from a specific IP.

4. CAPTCHA Before Login

After 3 failures → show CAPTCHA (e.g., Google reCAPTCHA).

5. Log Every Failed Attempt

Store IP, browser, timestamp in a separate table.

6. Admin Dashboard to Unlock Users

Allow support staff to manually reset lockout.

If you want any of these features, I can create a separate article with full code.

Part 6: Testing the Lockout System

Step 1

Try logging in with wrong password 5 times.

Expected behavior:

  • Counter increments

  • After 5 attempts → lockout message

  • Further attempts blocked

Step 2

Try logging in during the lockout time.

Expected behavior:

  • Error message:
    "Account locked. Try again later"

Step 3

Wait for lockout duration, then login with correct password.

Expected behavior:

  • Successful login

  • FailedLoginAttempts reset to 0

  • LockoutEnd cleared

Conclusion

You have now built a complete, secure Account Lockout System using:

  • ASP.NET Core backend

  • Angular frontend

  • SQL Server database

  • Tracking failed attempts

  • Automatic lockout and unlock

  • Login-flow updates

Account lockout is a powerful protection against brute-force and automated login attacks. Every serious application should implement it to improve security and protect user accounts.