Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@cvetkovv
Copy link
Contributor

Closes #33332

@cvetkovv cvetkovv requested a review from a team as a code owner September 27, 2024 12:03
@cvetkovv cvetkovv force-pushed the fix-external-token-validation branch from e6e0a6c to 4b4cd97 Compare September 27, 2024 12:05
@mposolda mposolda self-assigned this Oct 2, 2024
@mposolda mposolda requested a review from pedroigor October 2, 2024 08:25
@mposolda
Copy link
Contributor

mposolda commented Oct 2, 2024

@cvetkovv Thanks for the PR. The change makes sense to me. @pedroigor What do you think?

@cvetkovv Is it possible to add automated test for this change? For example you can take a look at KcOidcBrokerTokenExchangeTest .

@keycloak-github-bot
Copy link

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.broker.KcOidcBrokerTest#testPostBrokerLoginFlowWithOTP_bruteForceEnabled

Keycloak CI - Java Distribution IT (windows-latest - temurin - 17)

java.lang.AssertionError: Did not find the event of expected type USER_DISABLED_BY_TEMPORARY_LOCKOUT. Events present: [IDENTITY_PROVIDER_POST_LOGIN_ERROR, IDENTITY_PROVIDER_POST_LOGIN_ERROR]
	at org.junit.Assert.fail(Assert.java:89)
	at org.keycloak.testsuite.AssertEvents$ExpectedEvent.assertEvent(AssertEvents.java:403)
	at org.keycloak.testsuite.broker.AbstractAdvancedBrokerTest.testPostBrokerLoginFlowWithOTP_bruteForceEnabled(AbstractAdvancedBrokerTest.java:584)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...

Report flaky test

Copy link

@keycloak-github-bot keycloak-github-bot bot left a comment

Choose a reason for hiding this comment

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

Unreported flaky test detected, please review

Copy link
Contributor

@pedroigor pedroigor left a comment

Choose a reason for hiding this comment

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

@cvetkovv Can we do the other way around? Override the method on the Microsoft provider implementation instead?

The reason is that even though typ is optional for general JWTs by looking at the JWT specs, it is not for others types of tokens such as ID tokens.

Better have this constraint removed only for Microsft and enforce the claim for other providers. Or do we have other providers with the same problem?

@infl00p
Copy link
Contributor

infl00p commented Oct 22, 2024

I would like to request for this fix to be included in the Generic OIDC provider since it affects my use cases. I have integrated keycloak with proprietary OIDC implementations that do not include the optional typ field in the token body.
I would also like to request, if possible, to backport this fix to the KC25 release.
As you mentioned it's possible to create a custom social provider that overrides the token validation but until KC26 it was not possible to create multiple instances of it inside a realm.

@jonkoops
Copy link
Contributor

Unfortunately we will not be doing any more releases in the 25.x series, your best bet will be to upgrade to 26.x.

@timbrown5
Copy link

Can we do the other way around? Override the method on the Microsoft provider implementation instead?

The reason is that even though typ is optional for general JWTs by looking at the JWT specs, it is not for others types of tokens such as ID tokens.

We are seeing a similar issue with GitHub ID tokens. The typ field exists, but is set to JWT - so the #28866 breaks token exchange for us. Instead of disabling it for selected providers, I think it would it be better to make it an optional check, defaulting to enabled. This would mean it can disabled if required - e.g. the users is stuck with a third-party token provider that doesn't conform to rfc9062-2.1.
For reference, it looks like Microsoft (OP), Apple and GitHub tokens blocked by this check.

ref: #30021 (comment)

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Nov 6, 2024

I also stumbled upon this issue this week while trying to exchange an EntraID IDToken to a Keycloak access token, and I also failed on the missing JsonWebToken type.

How about setting the JsonWebToken.type(...) to TokenUtil.TOKEN_TYPE_BEARER in org.keycloak.broker.oidc.OIDCIdentityProvider#validateToken(java.lang.String, boolean) if the token type is missing?

I tried to refactor the JWT parsing logic in OIDCProvider which also sets the JsonWebToken.type(...) in my current branch: main...thomasdarimont:keycloak:issue/gh-xxxx-improve-jwt-token-parsing-in-oidc-identityprovider

With that in place I could successfuly exchange a EntraID ID token into a Keycloak Access token with the following request:

### External token to internal token exchange: EntraID IDToken to Keycloak
POST http://localhost:8081/auth/realms/entra-idp-demo/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_id=token-exchanger&client_secret=CLIENT_SECRET&subject_issuer=demo-idp-entraid&subject_token=ENTRAID_IDTOKEN&requested_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token_type=urn:ietf:params:oauth:token-type:id_token

@timbrown5
Copy link

...

With that in place I could successfuly exchange a EntraID ID token into a Keycloak Access token with the following request:

### External token to internal token exchange: EntraID IDToken to Keycloak
POST http://localhost:8081/auth/realms/entra-idp-demo/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_id=token-exchanger&client_secret=CLIENT_SECRET&subject_issuer=demo-idp-entraid&subject_token=ENTRAID_IDTOKEN&requested_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token_type=urn:ietf:params:oauth:token-type:id_token

FWIW, I have two comments on this:

  1. This suggestion will probably fix the OP's issue, but not the one I'm facing (and I think @asim46 is facing) - that the token typ is set, but it isn't set to an expected value.

  2. This seems to conflict with this earlier point:

The reason is that even though typ is optional for general JWTs by looking at the JWT specs, it is not for others types of tokens such as ID tokens.

If typ is required for an ID token in the spec then arguably the provider hasn't implemented the spec?

I think ignoring the typ field should be opt-in for such cases (and mine, where rfc9062-2.1 hasn't been implemented). Creating a situation where if typ is unset it will work but if it's set to something not in the expected values it won't is unintuitive/unfriendly for users and hides s potential vulnerability.

@cvetkovv
Copy link
Contributor Author

cvetkovv commented Nov 19, 2024

I added another PR with new opt-in param to skip the typ claim validation 35075 as suggested. On the other suggestion to skip the check in MicrosoftIdentityProvider is not possible as this provider is always using the userinfo endpoint to validate the token. But I still think that typ claim (defined in the JWT's payload and not in JWT's header) is something specific for keycloak, so its validation should be optional in OIDCIdentityProvider and required in KeycloakOIDCIdentityProvider. My understanding is that rfc9068-2.1 is referring to the typ header (which is set by the major providers) and not to typ claim, also openid-connect-core is not mentioning typ claim.

@timbrown5
Copy link

I added another PR with new opt-in param to skip the typ claim validation 35075 as suggested. On the other suggestion to skip the check in MicrosoftIdentityProvider is not possible as this provider is always using the userinfo endpoint to validate the token. But I still think that typ claim (defined in the JWT's payload and not in JWT's header) is something specific for keycloak, so its validation should be optional in OIDCIdentityProvider and required in KeycloakOIDCIdentityProvider. My understanding is that rfc9068-2.1 is referring to the typ header (which is set by the major providers) and not to typ claim, also openid-connect-core is not mentioning typ claim.

Makes sense. Maybe i wrong in my assumption/reading of the code that getType returned the type header, you're saying this is parsing just the claims?
The GItHub token we are seeing the issue with doesn't set the typ claim, just the header.
I agree that the RFC is talking about the typ header (see extract below) - could be that the Keycloak implementation is just non-standard?

Header:

   {"typ":"at+JWT","alg":"RS256","kid":"RjEwOwOA"}

Claims:

   {
     "iss": "https://authorization-server.example.com/",
     "sub": "5ba552d67",
     "aud":   "https://rs.example.com/",
     "exp": 1639528912,
     "iat": 1618354090,
     "jti" : "dbe39bf3a3ba4238a513f51d6e1691c4",
     "client_id": "s6BhdRkqt3",
     "scope": "openid profile reademail"
   }
[Figure 2](https://datatracker.ietf.org/doc/html/rfc9068#figure-2): [The Header and JWT Claims Set of a JWT Access Token](https://datatracker.ietf.org/doc/html/rfc9068#name-the-header-and-jwt-claims-s)

For reference, here is the decoded (and redacted) GitHub token

Token header
------------
{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "...",
  "x5t": "..."
}

Token claims
------------
{
  "actor": "...",
  "actor_id": "...",
  "aud": "...",
  "base_ref": "...",
  "enterprise": "...",
  "enterprise_id": "...",
  "event_name": "pull_request",
  "exp": 1731681421,
  "head_ref": "...",
  "iat": 1731681121,
  "iss": "https://token.actions.githubusercontent.com",
  "job_workflow_ref": "...",
  "job_workflow_sha": "...",
  "jti": "b645ac3a-...-bd6a9ad74ca6",
  "nbf": 1731680521,
  "ref": "...",
  "ref_protected": "...",
  "ref_type": "...",
  "repository": "...",
  "repository_id": "...",
  "repository_owner": "...",
  "repository_owner_id": "...",
  "repository_visibility": "...",
  "run_attempt": "1",
  "run_id": "...",
  "run_number": "206",
  "runner_environment": "self-hosted",
  "sha": "...",
  "sub": "...",
  "workflow": "Token Exchange self-test",
  "workflow_ref": "...",
  "workflow_sha": "..."
}

@cvetkovv
Copy link
Contributor Author

cvetkovv commented Nov 19, 2024

At least in OIDCIdentityProvider#validateToken JsonWebToken contains only the claims:

        JsonWebToken token;
        try {
            token = JsonSerialization.readValue(parseTokenInput(encodedToken, true), JsonWebToken.class);
        } catch (IOException e) {
            throw new IdentityBrokerException("Invalid token", e);
        }

which is parsed from the value of

    protected String parseTokenInput(String encodedToken, boolean shouldBeSigned) {
    ....
            return new String(jws.getContent(), StandardCharsets.UTF_8);

jws.getContent() returns just the claims without the headers

@DenEwout
Copy link

DenEwout commented Dec 17, 2024

This fix is not ideal. There are IDP's out there that follow https://datatracker.ietf.org/doc/html/rfc7519#section-5.1, by putting "JWT" in the "typ" claim. Making this validation configurable would be more desirable (#35075)

@berevlad
Copy link

Is there any ETA for this fix ?
Got stuck at Apple Login problem, the JWT from apple doens't contains "typ" and our apple login flow isn't working.

Thank you

@ahus1
Copy link
Contributor

ahus1 commented Mar 20, 2025

Closing due to #35075 now being merged.

@ahus1 ahus1 closed this Mar 20, 2025
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.

External token (not issued by Keycloak) cannot be validated in token exchange flow in case user info check is disabled

10 participants