Skip to content

Aut 1474 session cap reauth#137

Merged
andy-radchenko merged 4 commits into
masterfrom
AUT-1474-session-cap-reauth
Jul 7, 2026
Merged

Aut 1474 session cap reauth#137
andy-radchenko merged 4 commits into
masterfrom
AUT-1474-session-cap-reauth

Conversation

@andy-radchenko

@andy-radchenko andy-radchenko commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

AUT-1474: Re-authenticate instead of returning a session-capped refreshed token

Problem

Background: SSO session max lifespan

Every access/refresh token pair issued by Keycloak is tied to an underlying SSO session, and that session has a max lifespan (our Keycloak realm has this set to 12h). A refresh token can be used to mint new access tokens repeatedly, but only for as long as its parent session is alive — Keycloak will never issue a refreshed token that outlives the session's max lifespan, no matter how large the client's configured refresh-token TTL is. So as a session approaches its 12h cutoff, each successive refresh returns a token whose lifetime is capped to "whatever time is left on the session," which keeps shrinking toward zero rather than resetting to the normal TTL.

What changed

This behavior is new with our Keycloak 26 migration — Keycloak 1.9 did not cap a refreshed token's lifetime to the remaining session time, so Authenticator never had to deal with a refresh returning a shorter-than-usual token. Keycloak 26 does cap it, which exposes a gap in Authenticator's refresh handling:

Once a session gets within 90 seconds of ending, Authenticator.isValid() treats every access token as already expired, so each outgoing call triggers a refresh — and the refresh comes back capped just as short. This repeats on every call for as long as the session has under 90 seconds left, producing a burst of refresh calls that tracks request rate until the session hard-expires.

refreshOrRequestNewAccessToken only fell back to a full re-authenticate when the refresh call threw — never when the refresh succeeded but returned a session-capped token, so this refresh-loop had no escape hatch.

We're already observing this in the wild: a number of services are spamming the auth service with heavy bursts of refresh calls as their sessions near the 12h mark, matching this exact pattern.

While fixing this, we also found getAccessTokenInternal and refreshAccessToken computed refreshExpiresAt using getRefreshExpiresIn() * 100 instead of * 1000 (the expiresAt line right above each uses * 1000 correctly). This made the refresh-token window appear roughly 10x shorter than it actually is.

This is the java-api-sdk counterpart to AUT-1473, which addresses the same issue in authentication-api-sdk.

…icator

refreshExpiresAt was computed with a *100 multiplier instead of *1000,
making the refresh window appear roughly 10x shorter than the actual
refresh token TTL returned by the auth API.
…token

Keycloak caps a refreshed token's lifetime to whatever remains of the
parent SSO session. Once a session is within 90 seconds of ending,
isValid() treats every token as already expired, so each outgoing call
triggers a refresh, and the refreshed token comes back capped just as
short - producing a burst of refreshes tracking request rate until the
session hard-expires.

Authenticator.isSessionCapped() detects this case using the same 90s
threshold as isValid(), and refreshOrRequestNewAccessToken() now falls
through to a full re-authenticate when a refresh returns a
session-capped token instead of returning it.

AUT-1474
Comment on lines +151 to +152
// refreshExpiresAt must be computed with a *1000 (not *100) conversion,
// so it should still be refreshable just before the real TTL elapses.

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.

I propose to remove this comment because it makes sense only in the scope of this PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
{
return accessToken;
}
log.info("Refreshed token has a session-capped lifetime ({}s); re-authenticating.", authentication.getRefreshExpiresIn());

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.

minor: again I would put debug here or moved to debug log message below

@andy-radchenko andy-radchenko merged commit 5c1565f into master Jul 7, 2026
2 checks passed
@andy-radchenko andy-radchenko deleted the AUT-1474-session-cap-reauth branch July 7, 2026 08:20
andy-radchenko added a commit that referenced this pull request Jul 7, 2026
PR #137 was squash-merged into master as commit 5c1565f with subject
"Aut 1474 session cap reauth (#137)", which does not match the
conventional-commits format the release pipeline requires. As a
result, conventional-commits:validate found no releasable commit type
and CircleCI silently skipped the version bump and deploy.

This empty commit restores a conventional "fix:" subject on master so
the next CI run picks up a patch release, deploying the changes
already merged in #137.
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.

4 participants