{"record":{"id":"8803510ad5f75a3c","repo":"bitwarden/server","slug":"invalid-token-880351","errorCode":null,"errorMessage":"Invalid token.","messagePattern":"Invalid token\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Auth/Controllers/TwoFactorController.cs","lineNumber":149,"sourceCode":"    public async Task<TwoFactorAuthenticatorUpdateResponseModel> PutAuthenticator(\n        [FromBody] TwoFactorAuthenticatorUpdateRequestModel model)\n    {\n        var user = model.ToUser(await _userService.GetUserByPrincipalAsync(User));\n\n        var tokenIsValid =\n            _twoFactorAuthenticatorDataProtector.TryUnprotect(model.UserVerificationToken, out var decryptedToken)\n            && decryptedToken.Valid\n            && decryptedToken.TokenIsValid(user, model.Key);\n\n        if (!tokenIsValid)\n        {\n            throw new BadRequestException(\"UserVerificationToken\", \"User verification failed.\");\n        }\n\n        if (!await _userManager.VerifyTwoFactorTokenAsync(user,\n                CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token))\n        {\n            throw new BadRequestException(\"Token\", \"Invalid token.\");\n        }\n\n        await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);\n        return new TwoFactorAuthenticatorUpdateResponseModel(user);\n    }\n\n    [HttpPost(\"authenticator\")]\n    [Obsolete(\"This endpoint is deprecated. Use PUT /authenticator instead.\")]\n    public async Task<TwoFactorAuthenticatorUpdateResponseModel> PostAuthenticator(\n        [FromBody] TwoFactorAuthenticatorUpdateRequestModel model)\n    {\n        return await PutAuthenticator(model);\n    }\n\n    [HttpDelete(\"authenticator\")]\n    [ProducesResponseType(StatusCodes.Status204NoContent)]\n    public async Task<IActionResult> DeleteAuthenticator(\n        [FromBody] TwoFactorAuthenticatorDeleteRequestModel model)","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Auth/Controllers/TwoFactorController.cs#L131-L167","documentation":"BadRequestException with key 'Token' and message 'Invalid token.' is thrown in PUT /authenticator when _userManager.VerifyTwoFactorTokenAsync fails for the Authenticator provider against model.Token. This means the 6-digit TOTP the user typed does not match the time-based code computed from their registered authenticator secret.","triggerScenarios":"PUT /api/users/two-factor/authenticator (TwoFactorController line 149) where model.Token is wrong, expired (outside the TOTP time window), or computed from a different secret than the one registered for the user.","commonSituations":"Clock skew between the authenticator app and server, the user scanned the wrong QR / registered the secret on a second device, the code was entered after it rotated, or the secret was reset since minting the verification token.","solutions":["Have the user generate and submit a fresh code from the authenticator app immediately (within the 30s window).","Check for clock drift on the client device or server; TOTP allows a small window but large skew fails.","Re-register the authenticator (get-authenticator flow) if the secret may be out of sync.","Strip whitespace/formatting from the token before submission."],"exampleFix":"// before\napi.put('/users/two-factor/authenticator', { userVerificationToken, token: '12 34 56', key })\n// after: sanitize and submit current code\nconst token = code.replace(/\\s+/g, '');\napi.put('/users/two-factor/authenticator', { userVerificationToken, token, key });","handlingStrategy":"validation","validationCode":"const token = String(model.token).replace(/\\D/g, '');\nif (token.length !== 6) throw new Error('TOTP must be 6 digits');","typeGuard":"function isValidTotp(t): t is string { return /^\\d{6}$/.test(String(t)); }","tryCatchPattern":"try { await api.put('/users/two-factor/authenticator', model); }\ncatch (e) {\n  if (e.response?.data?.error?.errors?.Token) { throw new UserFacingError('The code is wrong or expired; generate a new one.'); }\n  throw e;\n}","preventionTips":["Strip non-digits from TOTP before submitting.","Submit within the 30s window; prompt the user to wait for a fresh code.","Re-register the authenticator secret if codes consistently fail."],"tags":["two-factor","totp","authenticator","token"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}