{"record":{"id":"9a304bdbbb9d7c53","repo":"bitwarden/server","slug":"externalid-cannot-exceed-300-characters","errorCode":null,"errorMessage":"ExternalId cannot exceed 300 characters.","messagePattern":"ExternalId cannot exceed 300 characters\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs","lineNumber":197,"sourceCode":"    }\n\n    private async Task<string> GetValidExternalIdAsync(Group group, string newExternalId)\n    {\n        if (string.IsNullOrWhiteSpace(newExternalId))\n        {\n            // Ensure we're not saving empty or just whitespace externalId.\n            return null;\n        }\n\n        await EnsureExternalIdIsValidAsync(group, newExternalId);\n        return newExternalId;\n    }\n\n    private async Task EnsureExternalIdIsValidAsync(Group group, string newExternalId)\n    {\n        if (newExternalId.Length > 300)\n        {\n            throw new BadRequestException(\"ExternalId cannot exceed 300 characters.\");\n        }\n\n        var existingGroups = await _groupRepository.GetManyByOrganizationIdAsync(group.OrganizationId);\n        if (existingGroups.Any(g => g.Id != group.Id &&\n                                    !string.IsNullOrWhiteSpace(g.ExternalId) &&\n                                    g.ExternalId.Equals(newExternalId, StringComparison.OrdinalIgnoreCase)))\n        {\n            throw new ConflictException(\"ExternalId already exists for another group.\");\n        }\n    }\n\n    private async Task AddMembersAsync(Group group, HashSet<Guid> usersToAdd)\n    {\n        // Azure Entra ID is known to send redundant \"add\" requests for each existing member every time any member\n        // is removed. To avoid excessive load on the database, we check against the high availability replica and\n        // return early if they already exist.\n        var groupMembers = await _groupRepository.GetManyUserIdsByIdAsync(group.Id, useReadOnlyReplica: true);\n        if (usersToAdd.IsSubsetOf(groupMembers))","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs#L179-L215","documentation":"Thrown as a BadRequestException (HTTP 400) by PatchGroupCommand.EnsureExternalIdIsValidAsync when the new externalId string supplied in a SCIM PATCH operation exceeds 300 characters. ExternalId is a customer-managed identifier from the IdP and is constrained to 300 characters in the Group entity schema.","triggerScenarios":"SCIM PATCH /v2/{organizationId}/Groups/{id} with a 'replace' operation on 'externalId' whose value string is longer than 300 characters. Typically caused by an IdP generating excessively long opaque identifiers or a misconfigured attribute mapping.","commonSituations":"An IdP (e.g., Entra ID) maps a long composite attribute (e.g., a full DN or concatenated GUIDs) to externalId. A custom SCIM client serializes an object instead of a short ID. A directory with deep nested OUs produces very long DNs.","solutions":["Shorten or hash the externalId value in the IdP attribute mapping to stay under 300 characters.","Map externalId to a stable, short identifier such as the directory object GUID.","Add a pre-flight length check in your SCIM client before sending the PATCH."],"exampleFix":"// before: IdP maps full DN to externalId\n//   \"externalId\": \"CN=Very Long Group Name,OU=Dept,OU=Groups,DC=corp,DC=example,DC=com,...(300+ chars)\"\n// after: map to object GUID\n//   \"externalId\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"","handlingStrategy":"validation","validationCode":"const MAX_EXTERNAL_ID = 300;\nif (externalId != null && externalId.Length > MAX_EXTERNAL_ID)\n    throw new InvalidOperationException($\"externalId exceeds {MAX_EXTERNAL_ID} chars\");\n// only then send PATCH","typeGuard":null,"tryCatchPattern":"try { await scimClient.PatchGroupExternalIdAsync(orgId, groupId, externalId); }\ncatch (ScimException ex) when (ex.StatusCode == 400 && ex.Message.Contains(\"ExternalId\"))\n{ /* shorten the externalId and retry */ }","preventionTips":["Map externalId to a short stable GUID in the IdP, not a full DN.","Enforce a 300-char client-side limit on all externalId values before sending.","Document the 300-char limit in your SCIM integration guide."],"tags":["scim","validation","groups","http-400","externalid","patch"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}