fullstackhero/dotnet-starter-kit · warning · UnauthorizedException

Unauthorized

Error message

Unauthorized

What it means

VerifyEnrollTwoFactorCommandHandler throws UnauthorizedException when ICurrentUser.IsAuthenticated() is false. Verification completes 2FA enrollment and enables it, so it must only run for a properly authenticated principal.

Solutions

  1. Sign in again (the enroll step too, if the key rotated) and retry with a valid token
  2. Keep the enroll→verify flow within one authenticated session; refresh the token if it is near expiry
  3. Confirm the endpoint enforces authentication and middleware order is correct
  4. In tests, stub ICurrentUser.IsAuthenticated() to true

Example fix

// before
if (Date.now() > tokenExpiry) verifyCode(code);
// after
if (Date.now() > tokenExpiry) await reauthenticate();
await verifyCode(code);
Defensive patterns

Strategy: try-catch

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: Posting the authenticator code without a bearer token, with an expired JWT, or calling the handler directly in tests without an authenticated ICurrentUser.

Common situations: Token expired while the user was reading the code from their authenticator app; frontend lost the auth header between enroll and verify-enroll calls; middleware misordering; unauthenticated test invocation.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/VerifyEnroll/VerifyEnrollTwoFactorCommandHandler.cs:29

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

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

    public async ValueTask<bool> Handle(
        VerifyEnrollTwoFactorCommand 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.");

        var sanitized = command.Code.Replace(" ", string.Empty, StringComparison.Ordinal);
        var valid = await _userManager.VerifyTwoFactorTokenAsync(
            user,
            _userManager.Options.Tokens.AuthenticatorTokenProvider,
            sanitized);

        if (!valid)
        {
            throw new CustomException(
                "The authenticator code is invalid.",
                errors: null,
                System.Net.HttpStatusCode.BadRequest);

View on GitHub (pinned to 3f2959e683)