{"record":{"id":"c1f8c1dc4524b332","repo":"bitwarden/server","slug":"the-model-state-is-invalid-c1f8c1","errorCode":null,"errorMessage":"The model state is invalid.","messagePattern":"The model state is invalid\\.","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Auth/Controllers/EmergencyAccessController.cs","lineNumber":195,"sourceCode":"        if (model.RequestHasNewDataTypes())\n        {\n            var result = await _emergencyAccessService.FinishRecoveryTakeoverAsync(\n                id,\n                user,\n                model.UnlockData!.ToData(),\n                model.AuthenticationData!.ToData());\n\n            if (result.Succeeded)\n            {\n                return;\n            }\n\n            foreach (var error in result.Errors)\n            {\n                ModelState.AddModelError(string.Empty, error.Description);\n            }\n\n            throw new BadRequestException(ModelState);\n        }\n\n        await _emergencyAccessService.PasswordAsync(id, user, model.NewMasterPasswordHash, model.Key);\n    }\n\n    [HttpPost(\"{id}/view\")]\n    public async Task<EmergencyAccessViewResponseModel> ViewCiphers(Guid id)\n    {\n        var user = await _userService.GetUserByPrincipalAsync(User);\n        var viewResult = await _emergencyAccessService.ViewAsync(id, user);\n        return new EmergencyAccessViewResponseModel(_globalSettings, viewResult.EmergencyAccess, viewResult.Ciphers, user);\n    }\n\n    [HttpGet(\"{id}/{cipherId}/attachment/{attachmentId}\")]\n    public async Task<AttachmentResponseModel> GetAttachmentData(Guid id, Guid cipherId, string attachmentId)\n    {\n        var user = await _userService.GetUserByPrincipalAsync(User);\n        var result =","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Auth/Controllers/EmergencyAccessController.cs#L177-L213","documentation":"Thrown by BadRequestException(ModelState) after an IdentityResult (from the password-change call on the grantee account) returned Succeeded == false. Each identity error is folded into ASP.NET ModelState, and serializing ModelState produces the generic message 'The model state is invalid.' The real cause lives in the per-field errors inside the ModelState, not this top-level string.","triggerScenarios":"POST/PUT to the emergency-access change-password endpoint (src/Api/Auth/Controllers/EmergencyAccessController.cs around line 195) where model.AuthenticationData is supplied but the underlying identity password change fails (e.g. new password rejected by policy, wrong current authentication data). result.Succeeded is false, errors are added to ModelState, then thrown.","commonSituations":"The supplied AuthenticationData does not match the grantee's stored credential, or NewMasterPasswordHash violates a server-side password policy / history rule. Also seen when the request model itself fails validation before reaching the identity call, leaving stale ModelState entries.","solutions":["Inspect the 'error' object array in the response body (the serialized ModelState) rather than the top-level message; it lists each failing field and description.","Verify the AuthenticationData payload (current password hash) is correct for the emergency-access grantee before resubmitting.","Confirm NewMasterPasswordHash satisfies the org/user password policy and has not been used recently (history enforcement).","If no field errors appear in ModelState, validate the request body against the request model schema client-side before sending."],"exampleFix":"// before: only logging top-level message\nconsole.log(err.response.data.message); // 'The model state is invalid.'\n// after: surface the per-field ModelState errors\nconst errors = err.response.data?.error?.errors ?? [];\nerrors.forEach(e => console.log(e.field, e.description));","handlingStrategy":"try-catch","validationCode":"const required = ['newMasterPasswordHash','key','authenticationData'];\nconst missing = required.filter(k => !(k in model));\nif (missing.length) throw new Error('Missing fields: ' + missing.join(','));","typeGuard":"function isValidPasswordChangeModel(m): m is PasswordChangeModel {\n  return typeof m?.newMasterPasswordHash === 'string' && m.newMasterPasswordHash.length > 0\n    && typeof m?.key === 'string'\n    && !!m?.authenticationData;\n}","tryCatchPattern":"try { await api.post(`/emergency-access/${id}/password`, model); }\ncatch (e) {\n  if (e.response?.status === 400 && e.response.data?.error?.errors) {\n    const detail = e.response.data.error.errors.map(x => `${x.field}: ${x.description}`).join('; ');\n    throw new Error(`Password change failed: ${detail}`);\n  }\n  throw e;\n}","preventionTips":["Always inspect the ModelState error array in 400 responses, not just the message.","Validate the request model fields client-side before sending.","Confirm the grantee's authentication data is current before change-password."],"tags":["emergency-access","identity","modelstate","password"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}