+
Skip to content

FAPI 2.0 Final - only accept its issuer identifier value as a string in the aud claim received in client authentication assertions #41133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 18, 2025

Conversation

tnorimat
Copy link
Contributor

closes #41119

@tnorimat
Copy link
Contributor Author

I confirmed that the Keycloak including the PR can pass FAPI 2.0 Final conformance tests.

@tnorimat
Copy link
Contributor Author

@mposolda If possible, could you check the PR?

@mposolda mposolda self-assigned this Jul 14, 2025
Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

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

@tnorimat Thanks! Added a comment. Could you please check it?

// Validate the client assertion audience
String issuerUrl = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName());
List<String> expectedAudiences = new ArrayList<>(Collections.singletonList(issuerUrl));
if (!token.hasAnyAudience(expectedAudiences)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it makes sense to also verify here that issuerUrl is the only audience and there are not any other audiences included?

So maybe something like:

if (!token.hasAnyAudience(expectedAudiences) && token.getAudience().length == 1) {

In the past, there was security issue found in the private_key_jwt authentication that servers accepted the JWT with multiple audiences and same JWT token, which was used for client authentication, was also used for some other party. That is why the recent draft of OIDC core mentions explicitly single audience https://openid.net/specs/openid-connect-core-1_0-36.html#ClientAuthentication . The FAPI2 seems to not mention it explicitly for the server, however it indirectly mention this as well in the requirements for client, where it mentions - The issuer identifier value shall be sent as a string not as an item in an array. . .

I am not 100% sure if it is possible to reliably check that aud was sent as a string and not an array (also there is likely not any security benefit in that). However for better security, it makes sense to me to check that the issuer is single audience and not other audiences are included. WDYT?

Copy link
Contributor Author

@tnorimat tnorimat Jul 16, 2025

Choose a reason for hiding this comment

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

Thank you for your comments.

As you suggested, I will add the "aud" check.

I know the secuirty issue about specification and you have resolved that.
#38819
#38830

OIDF FAPI 2.0 Final conformance tests include the test for check whether "aud" claim value is single.
So, when running the conformance tests, I do the following settings.

spi-login-protocol-openid-connect-allow-multiple-audiences-for-jwt-client-authentication=true

When a keycloak user tries to apply FAPI 2.0 Final security profile, they need to do the setting, which checks the "aud" single value check, so I omitted the same check in the PR.

However, if the PR includes the "aud" check, then the user need not do the setting because the PR itself checks that. It might be helpful for the user.

In order to do that, what about the following?

        if (token.getAudience().length != 1) {
            logger.warnf("invalid audience in client assertion - no audience or multiple audiences found");
            throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "invalid audience in client assertion");
        }
        if (!token.hasAnyAudience(expectedAudiences)) {
            logger.warnf("invalid audience in client assertion - audience not issuer URL");
            throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "invalid audience in client assertion");
        }

Copy link
Contributor Author

@tnorimat tnorimat Jul 16, 2025

Choose a reason for hiding this comment

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

I modified the PR as just above and confirmed that Keycloak incoporating the PR can pass FAPI 2.0 Security Profile Final and FAPI 2.0 Message Singing Final conformance tests with both the following settings (current Keycloak's default setting):

spi-login-protocol-openid-connect-allow-multiple-audiences-for-jwt-client-authentication=true

and

#spi-login-protocol-openid-connect-allow-multiple-audiences-for-jwt-client-authentication=true

The PR was force-pushed.

Copy link
Contributor

@mposolda mposolda Jul 17, 2025

Choose a reason for hiding this comment

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

@tnorimat Probably a detail, but I afraid a bit of NPE when there are not any audience in the token as in this case token.getAudience().length might probably throw NPE? Is it ok to rather change the condition for length to if (token.getAudience() == null || token.getAudience().length != 1) ?

Another option is to make sure that length is checked after token.hasAudience call (as token.hasAudience would fail in case that token does not have any audience and hence no chance of NPE).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mposolda Thank you. I will revise the PR as you suggested.

Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

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

@tnorimat Thanks, one last minor comment (to possibly prevent NPE)

// Validate the client assertion audience
String issuerUrl = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName());
List<String> expectedAudiences = new ArrayList<>(Collections.singletonList(issuerUrl));
if (!token.hasAnyAudience(expectedAudiences)) {
Copy link
Contributor

@mposolda mposolda Jul 17, 2025

Choose a reason for hiding this comment

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

@tnorimat Probably a detail, but I afraid a bit of NPE when there are not any audience in the token as in this case token.getAudience().length might probably throw NPE? Is it ok to rather change the condition for length to if (token.getAudience() == null || token.getAudience().length != 1) ?

Another option is to make sure that length is checked after token.hasAudience call (as token.hasAudience would fail in case that token does not have any audience and hence no chance of NPE).

…in the aud claim received in client authentication assertions

closes keycloak#41119

Signed-off-by: Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
@VinodAnandan
Copy link
Contributor

@mposolda @tnorimat Would it be possible to back-port this PR to the release/26.3 branch, please ?

@tnorimat
Copy link
Contributor Author

@mposolda I revised the PR as you suggested, and I am running the following OIDF conformance tests:

  • FAPI 2.0 Security Profile Final
  • FAPI 2.0 Message Signing Final
    I would appreciate it if you could re-check the PR.

Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

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

@tnorimat Thanks!

@mposolda mposolda merged commit 631aebd into keycloak:main Jul 18, 2025
77 checks passed
@mposolda
Copy link
Contributor

@VinodAnandan I afraid not. As you know, we are backporting mostly important bugfixes. And this issue does not fall into that category.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FAPI 2.0 Final - only accept its issuer identifier value as a string in the aud claim received in client authentication assertions
3 participants
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载