You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
FAPI 2.0: reject array-valued aud in client assertions - #54783
With the FAPI 2.0 / OAuth 2.1 client profiles (secure-client-authentication-assertion executor), a client authentication assertion whose aud claim is a single-element JSON array containing the issuer (for example ["https://host/realms/myrealm"]) was accepted. FAPI 2.0 Security Profile (Final), §5.3.2.1 requires the authorization server to accept only its issuer identifier, as a string, in the aud claim of client authentication assertions.
How
SecureClientAuthenticationAssertionExecutor previously decoded the assertion into JsonWebToken and validated the audience through getAudience(). Because the aud field is annotated with @StringOrArray, a JSON string and a single-element array are indistinguishable after deserialization, which let ["<issuer>"] pass both the length != 1 and the hasAnyAudience checks.
The executor now reads the assertion payload as a JSON tree and requires the aud claim to be a textual node equal to the realm issuer. All the other cases keep their existing behavior: missing audience, multiple audiences and non-issuer audiences are still rejected with invalid audience in client assertion.
The change is scoped to the executor, which is only active in the fapi-2-dpop-security-profile, fapi-2-dpop-message-signing and oauth-2-1-* default profiles. The general (non-FAPI) private_key_jwt / client_secret_jwt client authentication flow is untouched and continues to accept the array form, so the semantics of the allow-multiple-audiences-for-jwt-client-authentication realm option are unaffected.
This PR addresses #54743 by requiring a string-valued issuer audience in client assertions when the secure assertion executor applies, while leaving general JWT client authentication unchanged.
Changes:
Validate the raw JSON audience type and issuer value instead of the normalized token audience.
Add integration tests for string and array audiences, including acceptance without the policy.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The teardown leaves a login session active in the reused realm and WebDriver, causing subsequent tests to follow SSO and fail on a missing login form before checking the audience.
The realm and WebDriver are reused across test methods, but this teardown leaves the user's login session active. After the first case, oauth.doLogin() follows SSO and then tries to fill a nonexistent login form, causing subsequent cases to fail before checking the audience; log out the realm's sessions here or register equivalent per-test cleanup.
CI failure analysis — infra flake in the Admin UI E2E suite, the change is not in the failure path:
Admin UI E2E (chromium) (run 42315284087) failed on test/user-federation/kerberos.spec.ts:144 › User Fed Kerberos tests › Edit Kerberos provider › Should save several allowed Kerberos realms — locator.click timed out after 30s; the same job also shows multipl
BD4F
e switch:* toggles failing on the first attempt with "Clicking the checkbox did not change its state".
The same Admin UI E2E (chromium) job passed on main a few hours later (run 42336876762, job 128296713397), and this PR's own Account UI E2E (chromium) job passed in the same run.
The PR changes server-side FAPI 2.0 client-assertion aud validation (Java) and does not touch the Admin UI; all Keycloak CI suites, CodeQL, Guides, Documentation and the DCO check are green.
A rerun of the workflow is not available to external contributors, so I am leaving the evidence here instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #54743
What
With the FAPI 2.0 / OAuth 2.1 client profiles (
secure-client-authentication-assertionexecutor), a client authentication assertion whoseaudclaim is a single-element JSON array containing the issuer (for example["https://host/realms/myrealm"]) was accepted. FAPI 2.0 Security Profile (Final), §5.3.2.1 requires the authorization server to accept only its issuer identifier, as a string, in theaudclaim of client authentication assertions.How
SecureClientAuthenticationAssertionExecutorpreviously decoded the assertion intoJsonWebTokenand validated the audience throughgetAudience(). Because theaudfield is annotated with@StringOrArray, a JSON string and a single-element array are indistinguishable after deserialization, which let["<issuer>"]pass both thelength != 1and thehasAnyAudiencechecks.The executor now reads the assertion payload as a JSON tree and requires the
audclaim to be a textual node equal to the realm issuer. All the other cases keep their existing behavior: missing audience, multiple audiences and non-issuer audiences are still rejected withinvalid audience in client assertion.The change is scoped to the executor, which is only active in the
fapi-2-dpop-security-profile,fapi-2-dpop-message-signingandoauth-2-1-*default profiles. The general (non-FAPI)private_key_jwt/client_secret_jwtclient authentication flow is untouched and continues to accept the array form, so the semantics of theallow-multiple-audiences-for-jwt-client-authenticationrealm option are unaffected.Testing
Added
SecureClientAuthenticationAssertionExecutorTestcovering:aud= issuer as a string is accepted with the profileaud=["<issuer>"](single-element array) is rejected with the profile (the case from the issue)aud=["<issuer>", "<other>"]is rejected with the profileaud= token endpoint URL as a string is still rejected with the profileaud=["<issuer>"]is still accepted without the profile (general flow unchanged)The integration tests run in CI.