Uploaded image for project: 'Red Hat build of Keycloak'
  1. Red Hat build of Keycloak
  2. RHBK-3023

After migrating to newer Keycloak, token refreshes using inherited offline sessions return access tokens with invalid exp value [GHI#39021]

XMLWordPrintable

    • False
    • Hide

      None

      Show
      None
    • False

      Before reporting an issue

      [x] I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.

      Area

      oidc

      Describe the bug

      This is for migration from RH-SSO 7.6 to Keycloak 24.0.

      After they migrated their production environment to Keycloak 24.0.z, they attempted to refresh a token for an offline session migrated from RH-SSO, and the following was returned as the token response.

       {
        "access_token": "...",
        "expires_in": -1586880441,
        "refresh_expires_in": -1586880441,
        "refresh_token": "...",
        "token_type": "Bearer",
        "not-before-policy": 0,
        "session_state": "...",
        "scope": "... offline_access"
      }
      

      As you can see, the expires_in value is negative, which is invalid.
      The value is derived from the exp claim of the access token, and the issued access token has an exp value of 157680000 (1974-12-31 00:00:00 UTC), which is also invalid.

      {
        "exp": 157680000,
        "iat": 1744560441,
        "auth_time": 1709734954,
        "typ": "Bearer",
        ...
      

      The exp value of the access token is, roughly speaking, calculated as (session start time) + offlineSessionMaxLifespan.
      In this realm, offlineSessionMaxLifespan is set to 157680000 seconds (5 years), so it appears the result is from 0 + offlineSessionMaxLifespan.
      In other words, for some reason, the session start time is being treated as 0.

      The following is the code that likely considers the session start time to be 0:
      https://github.com/keycloak/keycloak/blob/81aa588ddc33de94884568aef0f9d46868ad8232/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java#L36-L40

      For recently created sessions, it seems that a note called startedAt is added to the offline session. However, in the customer's case, the session was created with an older version, so it appears that the startedAt note is not included [1].

      Therefore, clientSession.getStarted() in the following code becomes 0, leading SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp() to return 0 + offlineSessionMaxLifespan, which caused the access token's exp to be 157680000.
      https://github.com/keycloak/keycloak/blob/b9bd644dc5ef19830c2fec20427b3f1893fde830/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java#L1039-L1042

      Version

      24.0.z

      Regression

      [x] The issue is a regression

      Anything else?

      Consideration

      Is it possible that if clientSession.getStarted() is 0, then we use the iat of the offlineToken, which was sent to the endpoint? We should make sure to set startedAt, so that it is correctly set in the DB for the next time.

      Alternative is to update all client sessions at startup and set started_at in case that it is not set. But not sure if still possible and it can take long time as bulk updates could be long and people can have millions of sessions in the DB...

      Testing note

      There is AbstractMigrationTest.testOfflineTokenLogin() for testing migration of offline-token from the older version. Currently it is tested from Keycloak 19 or earlier, which I am not 100% sure if can be used to simulate the issue (as the issue is for migration from RH-SSO 7.6, which is Keycloak 18), but maybe yes. Hopefully we can maybe add calling the "introspection endpoint" to that testOfflineTokenLogin() to doublecheck if access-token has correct exp times (as introspection for such access-token would probably fail).

              Unassigned Unassigned
              pvlha Pavel Vlha
              Keycloak Core Clients
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated:
                Resolved: