-
Bug
-
Resolution: Done
-
Undefined
-
None
-
False
-
-
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).
- links to