-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Open
Labels
area/samlIndicates an issue on SAML areaIndicates an issue on SAML areakind/enhancementCategorizes a PR related to an enhancementCategorizes a PR related to an enhancementstatus/triageteam/core-clients
Description
Description
Keycloak parses the <OneTimeUse> condition in SAML 2.0 assertions (Section 2.5.1.5) but does not enforce
single-use semantics.
OneTimeConditionValidator performs syntactic validation only — it verifies no more than one
<OneTimeUse> element exists, but does not track whether an assertion has been previously consumed.
This means a SAML assertion containing <OneTimeUse> can be replayed within its validity window. This is
particularly relevant in IdP-initiated SSO flows where InResponseTo binding is absent, leaving no other
mechanism to detect replayed assertions.
Current Behavior
- External IdP sends assertion with
<Conditions><OneTimeUse/></Conditions> - Keycloak accepts the assertion and creates a session
- Same assertion is replayed (intercepted POST, browser manipulation)
- Keycloak accepts it again — no deduplication
Affected Code
org.keycloak.saml.validators.OneTimeConditionValidator— syntax check only, no state trackingorg.keycloak.saml.validators.ConditionsValidator— orchestrates condition validation- IdP-initiated broker flow — no
InResponseToto provide implicit replay protection
Value Proposition
- Closes a security gap in IdP-initiated SSO:
InResponseToprovides replay protection in SP-initiated
flows, but is absent in IdP-initiated flows.OneTimeUseenforcement fills this gap. - Spec compliance: SAML Core 2.0 Section 2.5.1.5 states relying parties "should maintain a cache of
the assertions it has processed" whenOneTimeUseis present. Keycloak currently ignores this. - Defense in depth: Even in SP-initiated flows, enforcing
OneTimeUseadds a second layer of replay
protection beyondInResponseTo.
Goals
- Detect and reject replayed SAML assertions when
<OneTimeUse>condition is present - Use Infinispan-backed cache (consistent with existing Keycloak caching patterns) to store consumed
assertion IDs - Cache TTL derived from assertion's
NotOnOrAftervalue — entries auto-evict after validity window,
preventing unbounded memory growth - Work correctly in clustered deployments via distributed Infinispan cache
- Cover both IdP-initiated and SP-initiated (broker) flows
Non-Goals
- Enforcing
OneTimeUsewhen the element is not present in the assertion (opt-in by the asserting party) - Replacing or modifying
InResponseTovalidation — this is an additional, independent check - Caching all assertions — only assertions with
<OneTimeUse>are tracked - Changing default SAML behavior for existing deployments — assertion replay rejection only applies when
<OneTimeUse>is explicitly included by the IdP
Discussion
No response
Notes
Proposed Implementation
- Register new Infinispan cache
samlOneTimeUseCachein cache configuration - Enhance
OneTimeConditionValidator:- On assertion with
<OneTimeUse>: check cache for assertion ID - If found → reject (
ValidationException: assertion replay detected) - If not found → insert with TTL =
NotOnOrAfter - now() - Fallback TTL (configurable, default 2h) when
NotOnOrAfteris absent
- On assertion with
- Estimated scope: ~300 lines (implementation + unit/integration tests)
Why Infinispan?
- Keycloak already uses Infinispan for session, auth session, and action token caches
- Distributed cache provides cross-node replay protection in clustered deployments
- Built-in TTL/eviction eliminates manual cleanup
I plan to contribute a pull request for this.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area/samlIndicates an issue on SAML areaIndicates an issue on SAML areakind/enhancementCategorizes a PR related to an enhancementCategorizes a PR related to an enhancementstatus/triageteam/core-clients