bitwarden/server · error · BadRequestException

ExternalId cannot exceed 300 characters.

Error message

ExternalId cannot exceed 300 characters.

What it means

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.

Solutions

  1. Shorten or hash the externalId value in the IdP attribute mapping to stay under 300 characters.
  2. Map externalId to a stable, short identifier such as the directory object GUID.
  3. Add a pre-flight length check in your SCIM client before sending the PATCH.

Example fix

// before: IdP maps full DN to externalId
//   "externalId": "CN=Very Long Group Name,OU=Dept,OU=Groups,DC=corp,DC=example,DC=com,...(300+ chars)"
// after: map to object GUID
//   "externalId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Defensive patterns

Strategy: validation

Validate before calling

const MAX_EXTERNAL_ID = 300;
if (externalId != null && externalId.Length > MAX_EXTERNAL_ID)
    throw new InvalidOperationException($"externalId exceeds {MAX_EXTERNAL_ID} chars");
// only then send PATCH

Try / catch

try { await scimClient.PatchGroupExternalIdAsync(orgId, groupId, externalId); }
catch (ScimException ex) when (ex.StatusCode == 400 && ex.Message.Contains("ExternalId"))
{ /* shorten the externalId and retry */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/9a304bdbbb9d7c53. Report an issue: GitHub.

Appendix: source

Thrown at bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs:197

    }

    private async Task<string> GetValidExternalIdAsync(Group group, string newExternalId)
    {
        if (string.IsNullOrWhiteSpace(newExternalId))
        {
            // Ensure we're not saving empty or just whitespace externalId.
            return null;
        }

        await EnsureExternalIdIsValidAsync(group, newExternalId);
        return newExternalId;
    }

    private async Task EnsureExternalIdIsValidAsync(Group group, string newExternalId)
    {
        if (newExternalId.Length > 300)
        {
            throw new BadRequestException("ExternalId cannot exceed 300 characters.");
        }

        var existingGroups = await _groupRepository.GetManyByOrganizationIdAsync(group.OrganizationId);
        if (existingGroups.Any(g => g.Id != group.Id &&
                                    !string.IsNullOrWhiteSpace(g.ExternalId) &&
                                    g.ExternalId.Equals(newExternalId, StringComparison.OrdinalIgnoreCase)))
        {
            throw new ConflictException("ExternalId already exists for another group.");
        }
    }

    private async Task AddMembersAsync(Group group, HashSet<Guid> usersToAdd)
    {
        // Azure Entra ID is known to send redundant "add" requests for each existing member every time any member
        // is removed. To avoid excessive load on the database, we check against the high availability replica and
        // return early if they already exist.
        var groupMembers = await _groupRepository.GetManyUserIdsByIdAsync(group.Id, useReadOnlyReplica: true);
        if (usersToAdd.IsSubsetOf(groupMembers))

View on GitHub (pinned to e93b962371)