{"record":{"id":"91372365dcbb97ce","repo":"fullstackhero/dotnet-starter-kit","slug":"unauthorized-disabletwofactorcommandhandler","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"warning","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Disable/DisableTwoFactorCommandHandler.cs","lineNumber":29,"sourceCode":"    : ICommandHandler<DisableTwoFactorCommand, bool>\n{\n    private readonly UserManager<FshUser> _userManager;\n    private readonly ICurrentUser _currentUser;\n\n    public DisableTwoFactorCommandHandler(UserManager<FshUser> userManager, ICurrentUser currentUser)\n    {\n        _userManager = userManager;\n        _currentUser = currentUser;\n    }\n\n    public async ValueTask<bool> Handle(\n        DisableTwoFactorCommand 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        // Require current password so a stolen access token alone can't downgrade\n        // account security.\n        if (!await _userManager.CheckPasswordAsync(user, command.CurrentPassword))\n        {\n            throw new UnauthorizedException(\"Current password is incorrect.\");\n        }\n\n        await _userManager.SetTwoFactorEnabledAsync(user, false);\n        await _userManager.ResetAuthenticatorKeyAsync(user);\n        return true;\n    }\n}","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Disable/DisableTwoFactorCommandHandler.cs#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log in again to obtain a fresh JWT and retry with the Authorization: Bearer header","Verify the endpoint is not marked [AllowAnonymous] and that UseAuthentication/UseAuthorization run before endpoint mapping in the correct order","Check JWT validation settings (issuer, audience, signing key, ClockSkew) so valid tokens are not rejected","In tests, stub ICurrentUser to return IsAuthenticated = true before calling the handler"],"exampleFix":"// before\nclient.DefaultRequestHeaders.Remove(\"Authorization\");\n// after\nclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", freshToken);","handlingStrategy":"try-catch","validationCode":"function canDisable2fa() {\n  return Boolean(accessToken) && !isTokenExpired(accessToken);\n}\nif (!canDisable2fa()) await reauthenticate();","typeGuard":null,"tryCatchPattern":"try {\n  await api.disableTwoFactor(cmd);\n} catch (e) {\n  if (e.status === 401) { await reauthenticate(); return retry(); }\n  throw e;\n}","preventionTips":["Refresh the access token before long-running account-security flows","Attach the Authorization header centrally in the API client, not per call","Never call protected endpoints from unauthenticated contexts"],"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"}