Skip to content

Sessions

This page helps you understand when users are asked to sign in, and when they go straight through.

When a user signs in, the auth server starts a session for their browser and names it in a cookie. The next time any client sends that browser to sign in, the session is enough: the user isn’t asked for their password again. That’s single sign-on. The session remembers who signed in, when, and how strongly, as its ACR level.

Each user can see their sessions under Account, Sessions in the admin console, and an administrator sees them on the user’s Sessions tab and on a client’s Sessions tab.

A session can be hours old. When an operation needs the user to have signed in recently, such as changing their payment details, ask for it.

  1. Add max_age to the authorization request, in seconds. This asks for a sign-in within the last five minutes:

    GET /auth/authorize?client_id=my-app&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=code&scope=openid&max_age=300&code_challenge=...&code_challenge_method=S256&state=... HTTP/1.1
    Host: auth.example.com

    A user who signed in longer ago than that enters their password again. One who signed in within it goes straight through.

  2. Check auth_time in the ID token before you allow the operation. It’s when the user last entered a credential, so it’s no older than max_age allows:

    {
    "sub": "c5b9b6a2-4b39-4b8e-9a8e-6f7f2b3a1d10",
    "auth_time": 1760000000,
    "acr": "urn:goiabada:level1"
    }

To ask for a password every time, whatever the session, send prompt=login instead. With both, prompt=login wins.

A session’s lifetimes, and how many sessions one user can hold, are under Admin, Sessions in the admin console, and apply to every client:

Setting Default
Idle timeout, in seconds 7200 (2 hours)
Maximum lifetime, in seconds 86400 (24 hours)
Maximum sessions per user 20 (0 means no limit)

A session can be used only while all of these hold:

Check Measured from Set by
Idle timeout The session’s last activity Idle timeout, in seconds
Maximum lifetime When the session started, so activity never extends it Maximum lifetime, in seconds
max_age When the user last entered a credential, which is auth_time. When the user’s two-factor authentication is removed, it’s when they entered the password, as ACR and AMR explains The authorization request, when it has one

Each bound includes its last second: a session idle for exactly the idle timeout still counts, and so does a sign-in exactly max_age seconds ago, as OpenID Connect Core 1.0 section 3.1.2.1 describes. The idle timeout can’t be longer than the maximum lifetime.

A session that fails one of them isn’t used, and the user signs in again. It isn’t ended either: a session that has merely expired revokes nothing, and its offline refresh tokens keep working. A background job removes idle and expired sessions every 12 hours.

A session’s last activity moves forward whenever it’s used:

Neither moves its maximum lifetime.

When a browser arrives with a session that’s valid and belongs to the user signing in, the user isn’t asked for their password, and goes on with the session’s ACR level. They’re asked for a one-time code only when the client needs a higher level than the session reached, which is a step-up. A request whose id_token_hint names someone else, or with prompt=login, asks for a password whatever the session, and for a one-time code whenever the level needs one.

A user who has been disabled can’t use a session. Disabling them ends their sessions, so their next sign-in starts from the password, which answers “Your user account is disabled.” once the right password is entered, and a silent request gets login_required. A session of a disabled user that’s still found ends the request with access_denied and “The user account is disabled.”

Once the user has signed in, the auth server checks them again before it records anything:

  • A disabled user is refused with access_denied, and no session is written.
  • A user whose credentials changed while they were signing in, because their password was changed or reset, signs in again from the password, with nothing written.

Then it records the sign-in in a session:

  • The browser’s session is valid and the user’s own. It’s reused: its last activity moves forward, and its ACR level goes up when this sign-in reached a higher one. Reusing it never lowers its level. When the user entered a credential in this sign-in, its auth_time is updated to then. A sign-in that asked for the password again, such as one with prompt=login, replaces the session’s level and methods with its own, as described in A session that’s already there.
  • Otherwise, a new session starts. It replaces the user’s own session the browser had, if it had one that’s no longer valid, and the user’s other sessions from the same browser, recognized by its device cookie. Their sessions in other browsers stay, unless the user would then hold more than the cap. Nothing those sessions handed out is revoked, though their normal refresh tokens stop working with them. A session the browser had for another user is ended, as Ending sessions describes.

A new session needs a password entered in this sign-in. A sign-in that was relying on a session that ended partway through goes back to the password step rather than starting one from nothing.

The cookie’s identifier changes whenever a session starts and whenever its ACR level goes up, so an identifier copied before the sign-in names nothing afterwards.

A browser can have up to five sign-ins in progress at once, each started by its own authorization request, and each finishes on its own. A user signing in to two clients from two tabs finishes both. A link or redirect to /auth/authorize from another site starts a sign-in of its own, or gets its own error, and leaves the others alone.

When a sixth starts, the one that moved least recently is pushed out. A sign-in moves whenever the user goes on to its next step, so one the user is working through outlives the tabs they abandoned. The next page of a pushed-out sign-in says “This request is no longer active”, and the user starts again from your app. Signing out ends every sign-in in progress in that browser. When a password or one-time code is accepted in a browser, any sign-in in progress there for a different user stops, so one abandoned after its password was entered doesn’t carry on into the next person’s session.

A page that keeps sending a window it opened to sign in can push the user’s sign-in out by starting five of its own. That costs the user a retry and nothing more: nothing is issued for a pushed-out sign-in.

The auth server gives each browser a cookie of its own, __Host-authserver_device, or authserver_device when its base URL is plain http, so a sign-in can tell which of the user’s sessions came from that browser. It holds a random identifier that only the auth server reads, and it’s used for nothing else: a first-party cookie the auth server needs to work, not a tracker.

  • It’s written when a sign-in starts a new session, for 400 days, the longest a browser keeps a cookie. A browser that already has one keeps its identifier.
  • Signing out doesn’t clear it, and neither does an administrator ending a session: it names the browser, not a session.
  • A session stores the identifier’s SHA-256, never the identifier, and neither is shown in the admin console, returned by an API or logged.
  • A browser whose cookies were cleared counts as a new browser. Its next sign-in leaves the old session alone, to expire or be ended by the cap.

The browser and IP address shown on a sessions page play no part: a second browser profile, or a colleague’s machine behind the same router, keeps its own session.

One person rarely needs more than a handful of sessions: a phone, a laptop, a browser at work. Maximum sessions per user caps them, from 0 to 1000.

When a sign-in starts a new session and leaves the user over the cap, the auth server ends as many of their other sessions as it takes, never the new one:

  1. Expired sessions first, those past their idle timeout or maximum lifetime that the background job hasn’t removed yet.
  2. Then the least recently used.

Ending a session this way revokes nothing it handed out, though its normal refresh tokens stop working with it. The audit log records it with the reason session_cap.

The cap catches pile-ups, such as a script that signs in with a fresh browser every time. Set 1 for one session per user, or 0 for no limit. Lowering it ends nothing at once: a user’s sessions over the new cap end at their next sign-in.

Closing the browser doesn’t end a session with the auth server. The cookie lasts as long as the session does, so reopening the browser within the idle timeout signs the user straight back in, and a restarted machine or a laptop shut for lunch costs nobody a password.

The admin console does the opposite on purpose. Its own cookie lasts only until the browser closes, so reopening the browser asks an administrator to sign in to it again, even while the auth server still has their session. An administrator’s browser reaches every part of the deployment, so one more sign-in is worth it to keep a lost or forgotten machine from still holding that access. Some browsers restore what was open after a crash or an update, and that can bring an admin console session back with it.