{"record":{"id":"a4003eef2eb434f3","repo":"fullstackhero/dotnet-starter-kit","slug":"unauthorized-verifyenrolltwofactorcommandhandler","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"warning","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/VerifyEnroll/VerifyEnrollTwoFactorCommandHandler.cs","lineNumber":29,"sourceCode":"    : ICommandHandler<VerifyEnrollTwoFactorCommand, bool>\n{\n    private readonly UserManager<FshUser> _userManager;\n    private readonly ICurrentUser _currentUser;\n\n    public VerifyEnrollTwoFactorCommandHandler(UserManager<FshUser> userManager, ICurrentUser currentUser)\n    {\n        _userManager = userManager;\n        _currentUser = currentUser;\n    }\n\n    public async ValueTask<bool> Handle(\n        VerifyEnrollTwoFactorCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        if (!_currentUser.IsAuthenticated())\n        {\n            throw new UnauthorizedException();\n        }\n\n        var userId = _currentUser.GetUserId().ToString();\n        var user = await _userManager.FindByIdAsync(userId)\n            ?? throw new NotFoundException($\"User {userId} not found.\");\n\n        var sanitized = command.Code.Replace(\" \", string.Empty, StringComparison.Ordinal);\n        var valid = await _userManager.VerifyTwoFactorTokenAsync(\n            user,\n            _userManager.Options.Tokens.AuthenticatorTokenProvider,\n            sanitized);\n\n        if (!valid)\n        {\n            throw new CustomException(\n                \"The authenticator code is invalid.\",\n                errors: null,\n                System.Net.HttpStatusCode.BadRequest);","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/VerifyEnroll/VerifyEnrollTwoFactorCommandHandler.cs#L11-L47","documentation":"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.","triggerScenarios":"Posting the authenticator code without a bearer token, with an expired JWT, or calling the handler directly in tests without an authenticated ICurrentUser.","commonSituations":"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.","solutions":["Sign in again (the enroll step too, if the key rotated) and retry with a valid token","Keep the enroll→verify flow within one authenticated session; refresh the token if it is near expiry","Confirm the endpoint enforces authentication and middleware order is correct","In tests, stub ICurrentUser.IsAuthenticated() to true"],"exampleFix":"// before\nif (Date.now() > tokenExpiry) verifyCode(code);\n// after\nif (Date.now() > tokenExpiry) await reauthenticate();\nawait verifyCode(code);","handlingStrategy":"try-catch","validationCode":"function canVerifyEnroll() {\n  return Boolean(accessToken) && !isTokenExpired(accessToken);\n}\nif (!canVerifyEnroll()) await reauthenticate();","typeGuard":null,"tryCatchPattern":"try {\n  await api.verifyEnrollTwoFactor({ code });\n} catch (e) {\n  if (e.status === 401) { await reauthenticate(); return retry(); }\n  throw e;\n}","preventionTips":["Verify promptly after enrolling so the token doesn't expire mid-flow","Centralize auth header handling","Refresh tokens before starting the verification step"],"tags":["authentication","jwt","identity","two-factor"],"backgroundTag":"authentication-required","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}