fullstackhero/dotnet-starter-kit · error · UnauthorizedAccessException

Invalid credentials.

Error message

Invalid credentials.

What it means

Thrown by the token generation handler after ValidateCredentialsAsync (which also covers 2FA verification when enabled) returns a null identity result. It means the email/password pair (or supplied two-factor code) failed authentication; the handler first records a failed-login security audit entry bound to the request's IP/user-agent/clientId, then throws.

Solutions

  1. Verify the email/password are correct and the account is not locked out
  2. Re-run the DbMigrator --seed if the user record is missing
  3. Check you're connecting to the intended database/environment

Example fix

// before
dotnet run --project src/Host/FSH.Starter.Api
// after
dotnet run --project src/Host/FSH.Starter.DbMigrator -- apply --seed
dotnet run --project src/Host/FSH.Starter.Api
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
    throw new ValidationException("Email and password are required before calling generate-token");

Try / catch

try { return await api.post('/tokens', { email, password }); }
catch (UnauthorizedAccessException) { showError('Invalid email or password'); return null; } // never reveal which one was wrong

Prevention

When it happens

Trigger: Wrong password, nonexistent email, account locked/disabled, or the password hash doesn't verify against the stored Identity user.

Common situations: Users typing wrong password; seed data reset so the account no longer exists; pointing the app at an empty/different database; Identity lockout after repeated failures.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Tokens/TokenGeneration/GenerateTokenCommandHandler.cs:73

        var ip = _requestContext.IpAddress ?? "unknown";
        var ua = _requestContext.UserAgent ?? "unknown";
        var clientId = _requestContext.ClientId;

        // Validate credentials (includes 2FA verification when the user has it enabled)
        var identityResult = await _identityService
            .ValidateCredentialsAsync(request.Email, request.Password, request.TwoFactorCode, cancellationToken);

        if (identityResult is null)
        {
            // 1) Audit failed login BEFORE throwing
            await _securityAudit.LoginFailedAsync(
                subjectIdOrName: request.Email,
                clientId: clientId!,
                reason: "InvalidCredentials",
                ip: ip,
                ct: cancellationToken);

            throw new UnauthorizedAccessException("Invalid credentials.");
        }

        // Unpack subject + claims
        var (subject, claims) = identityResult.Value;

        // 2) Audit successful login
        await _securityAudit.LoginSucceededAsync(
            userId: subject,
            userName: claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? request.Email,
            clientId: clientId!,
            ip: ip,
            userAgent: ua,
            ct: cancellationToken);

        // Issue token
        var token = await _tokenService.IssueAsync(subject, claims, /*extra*/ null, cancellationToken);

        // Persist refresh token (hashed) for this user

View on GitHub (pinned to 3f2959e683)