Skip to content

feat: implement configurable concurrent session limits per user and client#3964

Open
mshipkovenski wants to merge 2 commits into
cloudfoundry:developfrom
mshipkovenski:feature/session-limits-concurrent
Open

feat: implement configurable concurrent session limits per user and client#3964
mshipkovenski wants to merge 2 commits into
cloudfoundry:developfrom
mshipkovenski:feature/session-limits-concurrent

Conversation

@mshipkovenski

Copy link
Copy Markdown

Introduce numeric concurrent session limits for refresh tokens, replacing the previous binary refreshTokenUnique flag with a numeric cap.

Key changes:

  • TokenPolicy: the internal refreshTokenUnique field is now an int (positive value = max sessions, -1 = unlimited). The original boolean getters/setters (isRefreshTokenUnique / setRefreshTokenUnique) are preserved with @JsonIgnore for backwards compatibility. New getMaxSessionLimit / setMaxSessionLimit accessors operate on the integer value. The @JsonSetter accepts boolean or integer input so existing configurations continue to work.

  • ClientConstants: adds a REFRESH_TOKEN_UNIQUE constant for the additionalInformation key used for per-client overrides.

  • OauthEndpointBeanConfiguration: the jwt.token.refresh.unique property now accepts both boolean strings ("true"/"false") and positive integers, parsed at startup.

  • UaaTokenServices: replaces the all-or-nothing deleteRefreshTokensForClientAndUserId call with enforceConcurrentSessionLimit, which revokes only the oldest tokens when the active count would exceed the configured limit. A per-client override (stored in additionalInformation) takes precedence over the zone-level policy. Rotation-aware: token reuse and token replacement are detected to avoid counting a refresh as a new session.

  • SampleIdentityZone.json: refreshTokenUnique serialises as -1 (integer) instead of false to reflect the new type.

  • Tests: DeprecatedUaaTokenServicesTests, RefreshRotationTest, TokenPolicyTest, and the IdentityZone endpoint tests are updated to cover the new behaviour, including per-user isolation, public-client rotation, and confidential-client JTI-reuse scenarios.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 24, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: mshipkovenski / name: Miroslav Shipkovenski (593b9e1)

…lient

Introduce numeric concurrent session limits for refresh tokens, replacing
the previous binary refreshTokenUnique flag with a numeric cap.

Key changes:

- TokenPolicy: the internal `refreshTokenUnique` field is now an `int`
  (positive value = max sessions, -1 = unlimited). The original boolean
  getters/setters (`isRefreshTokenUnique` / `setRefreshTokenUnique`) are
  preserved with @JsonIgnore for backwards compatibility. New
  `getMaxSessionLimit` / `setMaxSessionLimit` accessors operate on the
  integer value. The @JsonSetter accepts boolean or integer input so
  existing configurations continue to work.

- ClientConstants: adds a REFRESH_TOKEN_UNIQUE constant for the
  additionalInformation key used for per-client overrides.

- OauthEndpointBeanConfiguration: the `jwt.token.refresh.unique` property
  now accepts both boolean strings ("true"/"false") and positive integers,
  parsed at startup.

- UaaTokenServices: replaces the all-or-nothing
  `deleteRefreshTokensForClientAndUserId` call with
  `enforceConcurrentSessionLimit`, which revokes only the oldest tokens
  when the active count would exceed the configured limit. A per-client
  override (stored in additionalInformation) takes precedence over the
  zone-level policy. Rotation-aware: token reuse and token replacement are
  detected to avoid counting a refresh as a new session.

- SampleIdentityZone.json: `refreshTokenUnique` serialises as -1 (integer)
  instead of false to reflect the new type.

- Tests: DeprecatedUaaTokenServicesTests, RefreshRotationTest, TokenPolicyTest,
  and the IdentityZone endpoint tests are updated to cover the new behaviour,
  including per-user isolation, public-client rotation, and confidential-client
  JTI-reuse scenarios.

Co-authored-by: Cursor <cursoragent@cursor.com>

ai-assisted=yes
@mshipkovenski mshipkovenski force-pushed the feature/session-limits-concurrent branch from db894b6 to 593b9e1 Compare June 24, 2026 19:54
@strehle strehle requested a review from Copilot June 25, 2026 14:11

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@fhanik

fhanik commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Can you please update the configuration reference too: https://github.com/cloudfoundry/uaa/blob/develop/docs/UAA-Configuration-Reference.md#jwttokenrefreshunique

The property now accepts a boolean or a positive integer. Update the
configuration reference to document:

- Type change from boolean to boolean | integer
- Semantics: false/0 = no limit, true = 1 session, N = N concurrent sessions
- Prerequisite: jwt.token.revocable must also be true
- Per-client override via refreshTokenUnique in additionalInformation
- Example yaml snippet showing a limit of 2 concurrent sessions
- Links to related properties jwt.token.revocable and jwt.token.refresh.rotate

Co-authored-by: Cursor <cursoragent@cursor.com>

ai-assisted=yes
@mshipkovenski

Copy link
Copy Markdown
Author

Can you please update the configuration reference too: https://github.com/cloudfoundry/uaa/blob/develop/docs/UAA-Configuration-Reference.md#jwttokenrefreshunique

Done

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Comment on lines +130 to 133
@JsonIgnore
public boolean isRefreshTokenUnique() {
return refreshTokenUnique;
return refreshTokenUnique > 0;
}
Comment on lines 64 to 70
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.MultitenantClientServices;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager;
import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Comment on lines +988 to +993
/**
* Enforces the per-user, per-client concurrent session limit by revoking the oldest refresh tokens so that
* at most {@code limit - 1} remain, leaving room for the refresh token that is about to be issued. A
* non-positive limit means unlimited and disables enforcement. This only takes effect when refresh tokens
* are revocable (i.e. {@code jwt.token.revocable=true}), which is guaranteed by the caller.
*/
Comment on lines +70 to +73
private static final String REFRESH_TOKEN_UNIQUE = "Maximum number of concurrent active refresh tokens (sessions) per user/client pair. " +
"Accepts a boolean or an integer for backwards compatibility: `true` is equivalent to `1` (one session), " +
"`false` or any non-positive value means no limit. A positive integer N allows up to N concurrent sessions. " +
"Only enforced when `jwtRevocable` is also `true`. Defaults to `false` (no limit).";
Comment on lines +1056 to +1058
When the limit is reached, the **oldest** refresh token is revoked to make room for the new
one. Enforcement only takes effect when `jwt.token.revocable` is also `true` (revocable
tokens must be enabled so UAA can track and delete them).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants