fullstackhero/dotnet-starter-kit · warning · CustomException

Account is temporarily locked due to too many failed login…

Error message

Account is temporarily locked due to too many failed login attempts. Try again later.

What it means

When the account is currently locked out (too many prior failed password attempts, per ASP.NET Identity lockout), the service throws CustomException with HTTP 423 Locked and this message instead of a generic 401. The lockout check intentionally runs before the password check.

Solutions

  1. Wait for the lockout window (default 5 minutes / configured LockoutOptions) to expire and retry with the correct password.
  2. An admin can unlock immediately via the identity module's unlock endpoint or by clearing LockoutEnd in the user store.
  3. Fix the credential source (secrets/env config) that caused the repeated failures, or add retry backoff in the calling client.

Example fix

// before: retry loop with no backoff
while (!ok) { ok = await LoginAsync(user, wrongPassword); }
// after: honor 423 and stop
if (response.StatusCode == HttpStatusCode.Locked) throw new ApiException("Account locked; wait before retrying");
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side: track consecutive failures and back off before hitting the threshold (default 5)
if (++failedAttempts >= 4) { showWarning('One more failed attempt locks the account.'); }

Try / catch

catch (ApiError e) when (e.StatusCode === 423) {
  const retryAt = parseRetryAfter(e) ?? Date.now() + 5 * 60_000;
  scheduleRetry(retryAt); // do not hammer
}

Prevention

When it happens

Trigger: POSTing valid-or-invalid credentials for a user whose LockoutEnd is in the future after exceeding MaxFailedAccessAttempts; repeated automated login retries against the same account.

Common situations: CI scripts with stale credentials hammering login; a user mistyped the password several times; misconfigured password in a deployed environment; brute-force protection triggered by a shared NAT IP with retries across accounts of one user.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/4fc6e41e0a41e71a. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/IdentityService.cs:199

        return tenant;
    }

    private async Task<FshUser> FindAndValidateUserByCredentialsAsync(string email, string password)
    {
        var user = await _userManager.FindByEmailAsync(email.Trim().Normalize());
        if (user is null)
        {
            // Generic 401 — never confirm or deny account existence from this path.
            throw new UnauthorizedException();
        }

        // Lockout check runs BEFORE password check so an attacker can't tell a locked
        // account from a wrong-password one on every request.
        if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user))
        {
            _logger.LogWarning("Login attempted for locked account {UserId}", user.Id);
            throw new CustomException(
                "Account is temporarily locked due to too many failed login attempts. Try again later.",
                errors: null,
                HttpStatusCode.Locked);
        }

        if (!await _userManager.CheckPasswordAsync(user, password))
        {
            if (_userManager.SupportsUserLockout)
            {
                await _userManager.AccessFailedAsync(user);
                if (await _userManager.IsLockedOutAsync(user))
                {
                    _logger.LogWarning(
                        "Account {UserId} locked out after exceeding failed login threshold.",
                        user.Id);
                }
            }
            throw new UnauthorizedException();

View on GitHub (pinned to 3f2959e683)