Repository navigation
Allow configuring "Revoke Refresh Token" per client (#16422) - #51798
thomasdarimont wants to merge 16 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds per-client overrides for refresh-token rotation and maximum reuse while preserving realm defaults.
Changes:
- Implements client-level token revocation behavior and validation.
- Adds Admin UI controls and integration coverage.
- Documents the settings and release impact.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
themes/.../messages_en.properties |
Adds validation message. |
tests/.../RefreshTokenRevokeClientOverrideTest.java |
Tests override behavior. |
DefaultClientValidationProvider.java |
Validates maximum reuse. |
Messages.java |
Defines validation key. |
TokenManager.java |
Resolves effective client settings. |
RefreshTokenIntrospectionProvider.java |
Applies override during introspection. |
AbstractRefreshTokenProvider.java |
Applies override during refresh. |
OIDCAdvancedConfigWrapper.java |
Exposes override accessors. |
OIDCConfigAttributes.java |
Defines client attributes. |
AdvancedTab.tsx |
Includes fields in reset handling. |
RefreshTokenRevocation.tsx |
Adds configuration controls. |
AdvancedSettings.tsx |
Integrates the controls. |
keycloak.v2/.../messages_en.properties |
Adds UI labels and help. |
timeouts.adoc |
Documents realm/client relationship. |
con-advanced-settings.adoc |
Documents client options. |
26_8_0.adoc |
Adds release note. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Revoke refresh token Disabled in Realm Edit: revised the UI according to Eriks suggestions: #51798 (comment) Client level Advanced Setting defaults to Realm setting |
9932d04 to
4322164
Compare
4322164 to
1a5b736
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (4)
services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java:222
Boolean.parseBooleanturns every malformed value intofalse, so a pre-existing client attribute (attributes were previously unrestricted) can silently disable refresh-token rotation even when the realm enables it. Parse only the two accepted literals and treat any other value as no override.
return StringUtil.isBlank(value) ? null : Boolean.parseBoolean(value.trim());
services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java:247
- This validation is applied to every client protocol, although these attributes are OIDC-only. Consequently, a SAML client that already uses either arbitrary attribute name with another value can no longer be updated; gate this validator on
OIDCLoginProtocol.LOGIN_PROTOCOL, as the nearby OIDC-only ACR validation does.
private void validateRefreshTokenRevocation(ValidationContext<ClientModel> context) {
ClientModel client = context.getObjectToValidate();
if (client == null) return;
docs/documentation/server_admin/topics/clients/oidc/con-advanced-settings.adoc:133
- This states that enabling the override always revokes a refresh token when used, but a positive Refresh Token Max Reuse explicitly permits reuse. Describe rotation as subject to the effective max-reuse setting so administrators are not misled about the resulting policy.
|The value overrides the realm option <<_revoke-refresh-token,Revoke Refresh Token>> for this client. When set to *Enabled*, refresh tokens issued to this client are revoked when used, regardless of the realm setting. When set to *Disabled*, refresh tokens issued to this client can be reused, even if revocation is enabled for the realm. When not set, the realm setting is used.
services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java:245
- A negative integer is returned as an effective override even though the new contract requires a non-negative value. A pre-existing attribute with such a value makes
currentCount > -1reject even the first refresh; invalid negative values should fall back to the realm setting just like non-integers.
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return null;
There was a problem hiding this comment.
Tested and works as documented:
- When realm setting is disabled, enabling revocation on a client takes precedence
- When realm setting is enabled, disabling revocation on a client takes precedence
- Max number of re-use takes precedence over realm configuration
Code looks good to me.
1a5b736 to
ad09076
Compare
|
Rebased on current main to retrigger ci |
Add client-level overrides for the realm settings "Revoke Refresh Token" and "Refresh Token Max Reuse" via the client attributes revoke.refresh.token and refresh.token.max.reuse. When unset, the realm setting is used. The overrides are exposed in the client's Advanced settings in the admin console and validated on client update. Fixes keycloak#16422 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Also reject malformed revoke.refresh.token values in client validation. Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Reverting the Advanced settings section wrote the raw JSON string of the
acr.loa.map attribute back into the form, while the form edits it as
key/value pairs. Once a client had been saved through this section
(which persists acr.loa.map as "{}"), clicking Revert and then Save
failed with "acr.loa.map.filter is not a function" and no request was
sent. Convert the attribute to key/value pairs when resetting, matching
how the client details form loads it.
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
- parse revoke.refresh.token strictly; malformed values inherit the realm setting - add dedicated message key for the revoke.refresh.token validation error - docs: grammar fix Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
…n revocation - admin UI: only explicit "true"/"false" override the realm setting, matching the server - treat negative refresh.token.max.reuse values as "inherit" at runtime Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
… revocation - Normalize the revoke.refresh.token attribute to its canonical form when loading it into the client form, so case/whitespace variants accepted by the server are shown as the matching select option - Cover an API-created non-canonical value in the advanced tab test - Clarify in the docs that enabling the override rotates refresh tokens and limits reuse according to Refresh Token Max Reuse Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
…n layout Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
…etup A malformed or negative refresh.token.max.reuse attribute (e.g. from an import) stayed in the form state and was resubmitted on every save of the Advanced tab, which the server validation now rejects while the field itself may be hidden. Normalize such values to "inherit" when the form is set up, in line with revoke.refresh.token, and make both normalizers tolerate the numeric form values stored by the NumberInput. Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
…lient form normalizer Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
- Canonicalize refresh token max reuse in the admin UI the way the server parses it (Integer.parseInt), so accepted spellings such as "+1" or "007" are not turned into inheritance when the client is opened and saved. - Remove the UI test block that seeded a malformed max reuse through the admin API, which the server validation rejects; the fallback is covered by the normalizer unit test. - Fix the docs: with "Revoke Refresh Token" disabled for a client the realm max reuse value is not used, reuse is unlimited. Related to keycloak#1642 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
…oken provider OID4VCIRefreshTokenProvider kept reading the realm-level "revoke refresh token" and "max reuse" settings, while the base class already applied the client-level overrides for the refresh lock. Use the effective settings via TokenManager in the OID4VCI rotation check as well, and add a test that rotates and replays an OID4VCI refresh token with the override enabled for the client and disabled for the realm. Related to keycloak#16422 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
fd2d628 to
89867c4
Compare
|
Rebased on current main. |
…t to 27 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
|
I moved the release note to 27_0_0.adoc |
There was a problem hiding this comment.
🟡 Changes recommended
With realm revocation disabled, the client override leaves /revoke unsynchronized with refresh rotation, while the admin UI test uses the wrong realm, causing assertion and update failures.
2 open issues
Review effort: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
When only the client override enables revocation, revocation bypasses the refresh lock and can race on session state, while the persistence test queries the wrong realm and fails its first assertion.
2 open issues
Review effort: Balanced
…nt lock The serialization lock taken by /revoke only consulted the realm setting, so a client that enables revocation through its override could have its client session read and removed concurrently with a rotating refresh. Resolve the effective setting via the authenticated client instead. Also pass the test realm to the admin client helpers in the advanced tab Playwright test, which otherwise looked the client up in master. Fixes keycloak#16422 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because client-side normalization differs from server parsing rules and can silently change revocation or max-reuse settings when imported clients are opened and saved.
No open issues 🎉
2 resolved since last review
Review effort: Balanced

Add client-level overrides for the realm settings "Revoke Refresh Token" and "Refresh Token Max Reuse" via the client attributes revoke.refresh.token and refresh.token.max.reuse. When unset, the realm setting is used. The overrides are exposed in the client's Advanced settings in the admin console and validated on client update.
This also adapts the VCIRefreshTokenProvider to honor the client level setting.
Fixes #16422