Limiting Concurrent Sessions Per User

Capping concurrent sessions is asked for by two very different groups: security teams who want to bound how many places a compromised credential can be used from, and commercial teams who want to stop one subscription being shared across a department. The mechanism is the same; the tuning is not. This page is part of the managing session state in Redis guide.

The Scenario This Solves

An account with an unlimited session count gives an attacker who obtains a credential somewhere quiet — a shared laptop, a phishing page, a leaked backup — an indefinite foothold that never competes with the legitimate user. A cap does not prevent the compromise, but it makes it visible: when the limit is reached, someone gets signed out and notices.

What a concurrent-session cap changes Without a cap an attacker's session coexists silently with the user's; with a cap the oldest session is evicted when the limit is reached, which surfaces the extra session as a visible sign-out. No limit attacker session sits alongside the user's, indefinitely nobody notices anything Capped oldest session evicted at the cap user is signed out unexpectedly the anomaly becomes visible
The security value is detection, not prevention: an unexplained sign-out is a signal the user can act on.

Implementing the Cap

The per-user session index makes this a small amount of code. On login, add the new session to the set, then trim the set to the limit by evicting the oldest entries.

const MAX_SESSIONS = 5;

export async function enforceSessionCap(userId: string, newSid: string) {
  const key = `user:${userId}:sessions`;
  await redis.zAdd(key, { score: Date.now(), value: newSid });   // sorted set, scored by time

  const excess = (await redis.zCard(key)) - MAX_SESSIONS;
  if (excess > 0) {
    const oldest = await redis.zRange(key, 0, excess - 1);       // lowest scores first
    await redis.del(oldest.map((sid) => `sess:${sid}`));
    await redis.zRem(key, oldest);
    await auditSessionEvicted(userId, oldest, "concurrent_limit");
  }
}

Use a sorted set scored by creation time rather than a plain set, so “oldest” is a range query rather than a scan with metadata lookups. Evict by creation time rather than last activity unless you have a specific reason otherwise: evicting the least recently used session sounds fairer but means a legitimate user who steps away for lunch loses their session to whoever logged in most recently, including an attacker.

Do the eviction as part of the login transaction. Adding the session first and trimming afterwards, without ordering, opens a window in which a burst of logins leaves more sessions than the cap allows — which is exactly the situation the cap exists to prevent.

Choosing the Number

The limit is a product decision with a security consequence, and the right value depends on how many devices a real user legitimately has.

Typical concurrent-session limits by product type Consumer products need enough sessions for phone, tablet, laptop and a work machine; internal business tools can be tighter; and high-assurance administrative access is often limited to a single session. Consumer 5–10 sessions phone, tablet, two laptops, a browser at work Business tool 3–5 sessions work machine, phone, occasional second browser Administrative 1–2 sessions deliberate friction, every extra login visible
Set it from measured behaviour, not intuition: look at the distribution of concurrent sessions per user before choosing, and pick a value above the 99th percentile of legitimate use.

Measure before you enforce. Run the counting logic in shadow mode for a couple of weeks and look at the distribution — you will almost certainly find a scattering of users with far more sessions than you expected, usually because sessions accumulate from browsers that were never signed out rather than from genuine simultaneous use. Shortening your absolute session lifetime often removes the need for a cap altogether.

Telling the User What Happened

An eviction with no explanation is indistinguishable from a bug, and users respond to it by logging in again — which evicts another session and can produce a loop between two devices that both keep re-authenticating.

Show a clear message on the evicted device: what happened, why, and what to do about it. Notify the account owner through a separate channel when an eviction occurs, because “you were signed out on your laptop because a new session started in another country” is exactly the alert that catches a compromise. And offer a device list where the user can see and end sessions themselves, so the cap becomes a tool they control rather than a rule that surprises them.

For shared-account situations — a team using one login for a tool that charges per seat — the honest fix is commercial rather than technical, but the cap makes the behaviour visible, which is usually the point. Expect support contacts when you first enable it, and prepare an answer.

Interactions With Other Session Rules

A cap does not exist in isolation, and three interactions catch people out.

The first is with remember-me. A product that issues long-lived sessions for convenience will accumulate them faster than users retire them, so a cap set for “devices in use” will start evicting sessions that are merely old rather than genuinely concurrent. Either shorten the absolute lifetime or count only sessions active in the recent past, and be explicit about which you chose.

The second is with session regeneration. Every privilege change mints a new identifier, and if the old entry is not removed from the index, a single user can appear to hold several sessions after one login with a step-up challenge. Remove the old identifier in the same operation that writes the new one, or your counts drift upward and users hit the cap without ever opening a second device.

The third is with impersonation and support access. A support session opened on behalf of a customer should not consume that customer’s session budget, and it should not be the session evicted when the customer logs in on a new device halfway through an investigation. Mark those sessions with their own type and exclude them from the cap, while still listing them in the audit trail.

Rolling It Out Without Support Tickets

Enabling a cap on a live product produces a wave of unexpected sign-outs unless it is staged, and the staging is straightforward.

Run the counting logic without enforcement first, and record how many users would have been affected each day. That number tells you whether your limit is right and gives you a list of accounts to look at — the outliers are usually shared logins or automation using a human account, both of which are worth knowing about independently.

Then enable enforcement for new sessions only, so nobody is signed out at the moment the change ships; the cap begins to bite as users naturally create sessions. Announce it in the product where the limit is visible, and make the device list available before the enforcement starts so users have somewhere to act.

Watch the eviction rate after enabling it. A steady trickle is the feature working; a spike concentrated on a few accounts is worth investigating individually, because it usually means either automation using a human login or a genuinely shared credential.

Keep a per-tenant override from the beginning. Enterprise customers will ask, and having the value configurable avoids a code change and a deploy every time somebody’s policy differs from your default.

Frequently Asked Questions

Should the limit evict the oldest session or reject the new login?

Evict the oldest in almost all cases. Rejecting the new login locks a legitimate user out of their current device because of a session they forgot about on another one, and it hands an attacker a denial-of-service tool: fill the cap and the real user cannot get in. Eviction keeps the person at the keyboard working while making the extra session visible.

Does a session cap improve security much?

Modestly, and mostly through detection rather than prevention. It does not stop a credential being used; it makes an extra session compete with a real one, which produces a sign-out the user notices. If the goal is bounding attacker access, shorter absolute session lifetimes and reuse detection on refresh tokens do more, and a cap complements them rather than replacing them.

How do I avoid a sign-out loop between two devices?

Never evict the session that is currently authenticating, and give newly created sessions a short grace period during which they are not eviction candidates. Without those two rules, two devices at the cap can each evict the other in turn every time their user retries, which looks like the application being broken rather than a policy working.

Should service accounts and integrations be capped?

Not with the same limit. Automation legitimately holds many concurrent credentials and does not have a user to notice a sign-out, so a human-shaped cap will break it at the worst moment. Give machine identities their own credential type with their own limits — or none — and keep the concurrent-session rule for interactive accounts, where the signal it produces has someone to receive it.

Where should the limit be configurable?

Per plan or per tenant, not per user, and with a global default. Enterprise customers frequently have policies of their own — some want one session, some want ten — and a value that can be set for a tenant avoids a code change each time. Keep it out of individual user records, or you end up with per-user exceptions nobody remembers granting.

Eviction Order at a Glance

Which session is evicted when the cap is reached Sessions are held in a sorted set scored by creation time, so the oldest entry is evicted first while the session currently authenticating is always exempt. oldest evicted first session 2 session 3 session 4 new login never evicted Scored by creation time, not last activity — otherwise a user at lunch loses to whoever logged in last.
Exempting the session currently authenticating is what prevents two devices evicting each other in a loop.