fullstackhero/dotnet-starter-kit · warning · UnauthorizedException

Unauthorized

Error message

Unauthorized

What it means

DisableTwoFactorCommandHandler throws UnauthorizedException when ICurrentUser.IsAuthenticated() returns false, meaning the request reached the handler without a valid authenticated user. It is a last-line guard protecting the 2FA disable flow, which must never run for anonymous callers.

Solutions

  1. Log in again to obtain a fresh JWT and retry with the Authorization: Bearer header
  2. Verify the endpoint is not marked [AllowAnonymous] and that UseAuthentication/UseAuthorization run before endpoint mapping in the correct order
  3. Check JWT validation settings (issuer, audience, signing key, ClockSkew) so valid tokens are not rejected
  4. In tests, stub ICurrentUser to return IsAuthenticated = true before calling the handler

Example fix

// before
client.DefaultRequestHeaders.Remove("Authorization");
// after
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", freshToken);
Defensive patterns

Strategy: try-catch

Validate before calling

function canDisable2fa() {
  return Boolean(accessToken) && !isTokenExpired(accessToken);
}
if (!canDisable2fa()) await reauthenticate();

Try / catch

try {
  await api.disableTwoFactor(cmd);
} catch (e) {
  if (e.status === 401) { await reauthenticate(); return retry(); }
  throw e;
}

Prevention

When it happens

Trigger: Calling the disable-2FA endpoint with no Authorization header, an expired/invalid JWT, a token missing the required claims after the endpoint's [AllowAnonymous] misconfiguration, or invoking the handler directly in tests without a mocked authenticated ICurrentUser.

Common situations: Expired access token after idle time; clock skew invalidating the JWT; auth middleware misconfigured or omitted on the route; a test harness that forgot to authenticate the current-user service.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Disable/DisableTwoFactorCommandHandler.cs:29

    : ICommandHandler<DisableTwoFactorCommand, bool>
{
    private readonly UserManager<FshUser> _userManager;
    private readonly ICurrentUser _currentUser;

    public DisableTwoFactorCommandHandler(UserManager<FshUser> userManager, ICurrentUser currentUser)
    {
        _userManager = userManager;
        _currentUser = currentUser;
    }

    public async ValueTask<bool> Handle(
        DisableTwoFactorCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        if (!_currentUser.IsAuthenticated())
        {
            throw new UnauthorizedException();
        }

        var userId = _currentUser.GetUserId().ToString();
        var user = await _userManager.FindByIdAsync(userId)
            ?? throw new NotFoundException($"User {userId} not found.");

        // Require current password so a stolen access token alone can't downgrade
        // account security.
        if (!await _userManager.CheckPasswordAsync(user, command.CurrentPassword))
        {
            throw new UnauthorizedException("Current password is incorrect.");
        }

        await _userManager.SetTwoFactorEnabledAsync(user, false);
        await _userManager.ResetAuthenticatorKeyAsync(user);
        return true;
    }
}

View on GitHub (pinned to 3f2959e683)