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 PatchUserCommand.HandleExternalIdOperationAsync when the new externalId for a user exceeds 300 characters. This mirrors the same 300-character constraint enforced for groups. The OrganizationUser entity caps ExternalId at 300 characters.

Source

Thrown at bitwarden_license/src/Scim/Users/PatchUserCommand.cs:121

        if (active && orgUser.Status == OrganizationUserStatusType.Revoked)
        {
            await _restoreOrganizationUserCommand.RestoreUserAsync(orgUser, EventSystemUser.SCIM);
            return true;
        }
        else if (!active && orgUser.Status != OrganizationUserStatusType.Revoked)
        {
            await _revokeOrganizationUserCommand.RevokeUserAsync(orgUser, EventSystemUser.SCIM, RevocationReason.Manual);
            return true;
        }
        return false;
    }

    private async Task HandleExternalIdOperationAsync(Core.Entities.OrganizationUser orgUser, string? newExternalId)
    {
        // Validate max length (300 chars per OrganizationUser.cs line 59)
        if (!string.IsNullOrWhiteSpace(newExternalId) && newExternalId.Length > 300)
        {
            throw new BadRequestException("ExternalId cannot exceed 300 characters.");
        }

        // Check for duplicate externalId (same validation as PostUserCommand.cs)
        if (!string.IsNullOrWhiteSpace(newExternalId))
        {
            var existingUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgUser.OrganizationId);
            if (existingUsers.Any(u => u.Id != orgUser.Id &&
                !string.IsNullOrWhiteSpace(u.ExternalId) &&
                u.ExternalId.Equals(newExternalId, StringComparison.OrdinalIgnoreCase)))
            {
                throw new ConflictException("ExternalId already exists for another user.");
            }
        }

        orgUser.ExternalId = newExternalId;
        await _organizationUserRepository.ReplaceAsync(orgUser);
    }
}

View on GitHub (pinned to e93b962371)

Solutions

  1. Map externalId to a short, stable identifier such as the directory object GUID or SAM account name.
  2. Add a client-side length check before sending the PATCH.
  3. Trim or hash long identifiers in the IdP attribute transformation rules.

Example fix

// before: IdP maps full DN
//   { "op":"replace", "path":"externalId", "value":"CN=...,OU=...,DC=...(300+)" }
// after: map to GUID
//   { "op":"replace", "path":"externalId", "value":"a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: PATCH /v2/{organizationId}/Users/{id} with a 'replace' on 'externalId' whose value exceeds 300 characters. Caused by an IdP mapping a long attribute (e.g., full DN, concatenated GUIDs) to externalId.

Common situations: IdP maps a long directory identifier to externalId. Custom SCIM client sends a serialized object or URL as externalId. Deeply nested AD OUs produce long DNs.

Related errors


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