{"record":{"id":"23dcc21395b07c05","repo":"bitwarden/server","slug":"user-verification-failed-23dcc2","errorCode":null,"errorMessage":"User verification failed.","messagePattern":"User verification failed\\.","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Auth/Controllers/TwoFactorController.cs","lineNumber":143,"sourceCode":"        var tokenable = new TwoFactorAuthenticatorUserVerificationTokenable(user, data.Key);\n        var userVerificationToken = _twoFactorAuthenticatorDataProtector.Protect(tokenable);\n        return new TwoFactorAuthenticatorResponseModel(data, userVerificationToken);\n    }\n\n    [HttpPut(\"authenticator\")]\n    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);","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Auth/Controllers/TwoFactorController.cs#L125-L161","documentation":"BadRequestException with key 'UserVerificationToken' is thrown in PUT /authenticator (PutAuthenticator) when the user-verification token fails one of: TryUnprotect (tampered/corrupt), decryptedToken.Valid (expired signature), or decryptedToken.TokenIsValid(user, model.Key) (wrong user or key). This is a data-protection token bound to the requesting user and their key, minted by the get-authenticator endpoint.","triggerScenarios":"PUT /api/users/two-factor/authenticator (TwoFactorController line 143) submitted with a UserVerificationToken that is missing, expired, decrypted-but-invalid, or bound to a different user/key than the current principal.","commonSituations":"The client reused a token from a different session/user, the token expired before submission, the user changed their master key between minting and using the token, or the get-authenticator step (which issues the token) was skipped.","solutions":["Re-call POST /two-factor/get-authenticator (with secret verification) to mint a fresh UserVerificationToken, then submit it immediately.","Ensure the same authenticated user and master key are used for both minting and the PUT.","Do not cache or reuse the token across logins; treat it as single-use and short-lived.","Confirm the token string is not truncated/altered in transit (encoding)."],"exampleFix":"// before: reusing an old/cached token\napi.put('/users/two-factor/authenticator', { userVerificationToken: oldToken, ... })\n// after: mint fresh each flow\nconst { userVerificationToken } = await api.post('/users/two-factor/get-authenticator', { masterPasswordHash });\napi.put('/users/two-factor/authenticator', { userVerificationToken, token, key });","handlingStrategy":"validation","validationCode":"if (!model.userVerificationToken) { const r = await api.post('/users/two-factor/get-authenticator', { masterPasswordHash }); model.userVerificationToken = r.userVerificationToken; }","typeGuard":"function hasFreshVerificationToken(m, mintedAt): m is AuthenticatorUpdateModel & { userVerificationToken: string } {\n  return typeof m.userVerificationToken === 'string' && Date.now() - mintedAt < 5 * 60 * 1000;\n}","tryCatchPattern":"try { await api.put('/users/two-factor/authenticator', model); }\ncatch (e) {\n  if (e.response?.status === 400 && e.response.data?.error?.errors?.UserVerificationToken) {\n    model.userVerificationToken = (await api.post('/users/two-factor/get-authenticator', { masterPasswordHash })).userVerificationToken;\n    return api.put('/users/two-factor/authenticator', model);\n  }\n  throw e;\n}","preventionTips":["Mint a fresh UserVerificationToken immediately before each mutating two-factor call.","Never reuse tokens across users, sessions, or providers.","Treat verification tokens as short-lived and single-use."],"tags":["two-factor","authenticator","user-verification-token","data-protection"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}