{"record":{"id":"4979ca35d946a03c","repo":"fullstackhero/dotnet-starter-kit","slug":"the-authenticator-code-is-invalid","errorCode":null,"errorMessage":"The authenticator code is invalid.","messagePattern":"The authenticator code is invalid\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"warning","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/VerifyEnroll/VerifyEnrollTwoFactorCommandHandler.cs","lineNumber":44,"sourceCode":"\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);\n        }\n\n        await _userManager.SetTwoFactorEnabledAsync(user, true);\n        return true;\n    }\n}\n","sourceCodeStart":26,"sourceCodeEnd":54,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/VerifyEnroll/VerifyEnrollTwoFactorCommandHandler.cs#L26-L54","documentation":"VerifyEnrollTwoFactorCommandHandler throws CustomException('The authenticator code is invalid.') with HTTP 400 when VerifyTwoFactorTokenAsync returns false for the (space-stripped) code. The secret was generated, but the supplied 6-digit TOTP did not match within the allowed time window.","triggerScenarios":"Typing a wrong or old code; device clock skew pushing the TOTP outside the validation window; scanning the QR from a previous (rotated) enrollment; code reuse past the one-time window.","commonSituations":"User's phone clock is off by minutes; user rescanned an old QR after re-enrolling; autocomplete filled a stale code; user entered the recovery key instead of a TOTP.","solutions":["Wait for a fresh 30-second code and retry immediately","Fix device clock (enable automatic time/NTP sync on the authenticator device)","Re-run the enroll step to get a new QR/secret, then scan and verify with a fresh code","Verify the client strips spaces and sends the 6-digit code exactly (the handler already removes spaces)","Ensure the issuer/digits (6) configured in the otpauth URI match the authenticator app entry"],"exampleFix":"// before\nawait verifyEnroll(codeFromOldQr);\n// after\nawait enrollTwoFactor();   // rotates secret, returns fresh QR\nconst fresh = await readFreshTotp();\nawait verifyEnroll(fresh);","handlingStrategy":"validation","validationCode":"function validateTotp(code) {\n  const clean = (code ?? '').replace(/\\s+/g, '');\n  return /^\\d{6}$/.test(clean) ? clean : null;\n}\nconst clean = validateTotp(userInput);\nif (!clean) show('Enter the 6-digit code from your authenticator.');","typeGuard":null,"tryCatchPattern":"try {\n  await api.verifyEnrollTwoFactor({ code: clean });\n} catch (e) {\n  if (e.status === 400 && /authenticator code is invalid/i.test(e.message)) {\n    attempts++;\n    show(attempts >= 3 ? 'Re-scan the QR and try a fresh code.' : 'Wrong code — wait for the next one.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Enable automatic time sync (NTP) on the user's device","Always re-enroll (fresh QR) after a failed or abandoned enrollment","Strip spaces client-side and enforce 6 digits before submitting","Show a live countdown to the next 30-second TOTP window"],"tags":["two-factor","totp","validation","identity"],"backgroundTag":"totp-code-invalid","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"}