8000 FAPI 2.0: reject array-valued aud in client assertions by Inf1mum · Pull Request #54783 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
8000

FAPI 2.0: reject array-valued aud in client assertions - #54783

Open
Inf1mum wants to merge 1 commit into
keycloak:mainfrom
Inf1mum:fix/54743-fapi-aud-array
Open

Inf1mum wants to merge 1 commit into
keycloak:mainfrom
Inf1mum:fix/54743-fapi-aud-array

Conversation

@Inf1mum
@Inf1mum Inf1mum commented Nov 6, 2026
Copy link
Copy Markdown
Contributor

Closes #54743

What

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.

Testing

Added SecureClientAuthenticationAssertionExecutorTest covering:

  • aud = issuer as a string is accepted with the profile
  • aud = ["<issuer>"] (single-element array) is rejected with the profile (the case from the issue)
  • aud = ["<issuer>", "<other>"] is rejected with the profile
  • aud = token endpoint URL as a string is still rejected with the profile
  • aud = ["<issuer>"] is still accepted without the profile (general flow unchanged)

The integration tests run in CI.

Signed-off-by: Inf1mum <Inf1mum@users.noreply.github.com>
@Inf1mum
Inf1mum requested a review from a team as a code owner November 6, 2026 17:17
Copilot AI balanced review requested due to automatic review settings November 6, 2026 17:17
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Shared SSO cookies are not cleared between tests, so subsequent tests skip the login form and fail before exercising their audience assertions.

1 open issue
What changed in this PR

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.
File Description
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​client/​policies/​SecureClientAuthenticationAssertionExecutorTest.java Adds audience validation and non-policy compatibility tests.
services/​src/​main/​java/​org/​keycloak/​services/​clientpolicy/​executor/​SecureClientAuthenticationAssertionExecutor.java Requires aud to be a JSON string equal to the realm issuer.

Review effort: Balanced

}

private AccessTokenResponse requestTokenWithAudience(JsonNode aud) {
oauth.doLogin("test-user@localhost", "password");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FAPI 2.0 client profile accepts a client assertion whose aud is a single-element array

2 participants

0