ADR-0005: Support Forward Slashes in Entity IDs via URL Encoding
Status
Proposed
Context
Camunda's Identity REST API uses entity IDs (group, role, tenant, user, mapping rule) as path
segments in URLs — for example, PUT /v2/roles/{roleId}/groups/{groupId}. When Camunda is
configured with an OIDC provider such as Keycloak, entity IDs are sourced directly from the
identity provider. Keycloak group IDs commonly contain forward slashes (e.g., /myGroup,
/org/team/engineering).
When the frontend constructs a URL with a literal / in an entity ID, the resulting path (e.g.,
PUT /v2/roles/admin/groups//myGroup) contains extra path segments. Spring MVC finds no matching
route and returns 400. This effectively prevents any OIDC-sourced entity with / in its ID from
being managed through the REST API or the Identity UI.
Two compounding issues cause this:
- Validation allows
/:SecurityConfiguration.DEFAULT_EXTERNAL_ID_REGEXis.*when the OIDCgroupsClaimis configured, permitting any character in group IDs — including/. - Frontend does not encode: URL paths are constructed via template literals without
encodeURIComponent, so special characters are inserted verbatim.
See: https://github.com/camunda/camunda/issues/45215
Decision
We will use standard URL encoding (%2F) on the client side and configure the embedded Tomcat
server to pass encoded slashes through to Spring MVC. Entity IDs will not be normalized or
transformed — they must match exactly what the identity provider returns.
The solution has three layers:
1. Frontend: Encode IDs with encodeURIComponent
Every entity ID interpolated into a URL path is wrapped with encodeURIComponent(). This is
standard HTTP behavior per RFC 3986 and is a no-op for alphanumeric strings, making it safe for
all existing IDs.
// Before
apiPut(`${ROLES_ENDPOINT}/${roleId}/groups/${groupId}`);
// After
apiPut(`${ROLES_ENDPOINT}/${encodeURIComponent(roleId)}/groups/${encodeURIComponent(groupId)}`);
2. Tomcat: PASSTHROUGH mode for encoded slashes
Tomcat 11 rejects %2F in URL paths by default (EncodedSolidusHandling.REJECT). A
WebServerFactoryCustomizer bean sets the handling mode to PASSTHROUGH, which forwards %2F
as-is to the application without decoding or rejecting it.
connector.setEncodedSolidusHandling("passthrough");
The DECODE mode was not chosen because it decodes %2F to / before Spring MVC sees the
request, which would break route matching in the same way as the original unencoded slash.
3. Spring MVC: PathPatternParser (no changes needed)
Spring Boot's default PathPatternParser splits paths on literal / characters only. An encoded
%2F is not a path separator, so /v2/roles/admin/groups/%2FmyGroup correctly matches
/v2/roles/{roleId}/groups/{groupId}. Spring then decodes %2F to / when binding to the
@PathVariable parameter. This is the default behavior — no configuration changes are required.
Alternatives Considered
Normalize IDs (strip or replace slashes)
Rejected. Entity IDs must match the identity provider exactly. Normalizing would create a mismatch between Camunda's stored ID and the ID in OIDC tokens, breaking authorization checks.
Use request body instead of path variables
Rejected. This would require changing the REST API contract, breaking backward compatibility for all API consumers. URL encoding is the standard HTTP mechanism for this problem.
Custom path matching or wildcard patterns
Rejected. Using ** patterns or custom PathMatcher configuration would be fragile, make routing
ambiguous, and require changes across all controllers. Standard URL encoding solves the problem
without any routing changes.
Consequences
Positive
- Entity IDs from OIDC providers are supported as-is, regardless of special characters.
- The solution follows HTTP standards (RFC 3986) — no custom encoding schemes.
- Fully backward compatible:
encodeURIComponentis a no-op on alphanumeric strings, andPASSTHROUGHonly affects requests that contain%2F. - No changes to controllers, service layer, or data model.
Negative
- Reverse proxies must forward raw URIs. Proxies like nginx may reject or decode
%2Fby default. Operators using reverse proxies must configure them to preserve encoded characters (e.g., nginx:proxy_passwith$request_uri, ormerge_slashes off). - External API clients must encode IDs. Any client calling the REST API directly must URL-encode entity IDs containing special characters. This is standard HTTP behavior but must be documented in the API reference.
PASSTHROUGHapplies to all connectors. The Tomcat configuration affects all incoming requests, not just identity endpoints. This is safe because%2Fin a path segment is only meaningful if the application interprets it — existing endpoints with alphanumeric IDs are unaffected.