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
- Wait for the lockout window (default 5 minutes / configured LockoutOptions) to expire and retry with the correct password.
- An admin can unlock immediately via the identity module's unlock endpoint or by clearing LockoutEnd in the user store.
- 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
- Never retry login in a tight loop; use exponential backoff.
- Keep credentials in a secret manager, not inline config, to avoid stale-password storms.
- Monitor 423 responses in logs — bursts indicate a misconfigured automation job.
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
- Access token subject mismatch.
- Cannot create a ticket without an authenticated reporter.
- Cannot post a comment without an authenticated author.
- email not confirmed
- Invalid credentials.
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)