skip to content

What does your service provider store at login so that a later logout message naming a SessionIndex can find the right session?

level: seniorimportance: should knowfreq 32%

answer

  1. two namespaces, joined at login
  2. issuer plus subject plus index
  3. one index, possibly several sessions
  4. deleting the record ends it
  5. arriving twice is a non-event

basics

~20 s

Record, against the local session you issue, the issuing counterparty, the subject identifier as sent, and the SessionIndex from the assertion — indexed so a lookup by those three is a single read. Without that row the inbound message names a session at the other side that you cannot resolve to one of yours.

solid answer

~50 s

The identity provider names the session it is ending in its own namespace: a subject identifier plus a `SessionIndex` that means something only to it. My local session record is in a different namespace entirely. So at the moment I issue a session I write the join: issuer, the `NameID` exactly as received including its format and qualifiers, and the `SessionIndex` from the assertion, indexed for lookup. When a `LogoutRequest` arrives naming that index, I resolve it to my own session ids and end those records — ending the server-side record is what ends the session; clearing the browser cookie is best-effort, because the browser may never come back. If the message names only a subject and no index, I end that subject's sessions for that issuer. And the whole path is idempotent: a second copy, an unknown index and an already-expired session must all be non-events rather than errors.

code

json · 11 lines
json
{
  "localSessionId": "sess_01J9Q4C7",
  "issuerEntityId": "https://idp.northbank-drainage.example/entity",
  "nameId": {
    "value": "a7f1c0e4-88b2-4f31-9d0a-2c5f1b3e7a90",
    "format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
    "spNameQualifier": "https://permits.example/sp"
  },
  "sessionIndex": "_5c2a91f0e7b4",
  "issuedAt": "2026-09-19T05:41:02Z"
}

go deeper

for a junior

Know that the identity provider names its own session when it asks you to end one, and that your server has to have recorded which of its sessions that corresponds to.

for a middle

Explain the join row — issuer, subject as received, session index, local session id — and why the mapping is one-to-many in both directions rather than one-to-one.

for a senior

Show the operational properties: an indexed lookup, a teardown visible to every replica, an audit entry per teardown, and idempotent handling of duplicates, unknown indexes and already-expired sessions.

for a principal

Frame this as the contract you offer a customer's security team: what a federated teardown is guaranteed to end, what it is not, and what evidence you can produce afterwards that it happened.

## Two namespaces that have to be joined at login When a contractor's worker signs in to the permit system, two sessions come into existence. One belongs to the firm's identity provider — it authenticated the person and it keeps its own record. The other is yours: a server-side session record and a cookie in that worker's browser. The identity provider knows nothing about your record. When it later wants that sign-in ended, it names what it knows: the subject and a `SessionIndex`, which is its own handle for its own session. Your job, and the whole of this leaf's share of logout, is to have written down the join that turns that handle into your session ids. If you did not write it at login, there is nothing to look up later, and the message is unactionable however correct it is. ## What the row holds Against each local session you issue through a federated sign-in, record: - the **issuing counterparty**, as its `entityID` — the index is only meaningful within one issuer; - the **subject identifier exactly as received**, including its format and any qualifiers, because you have to match on what arrives, not on a normalised copy; - the **`SessionIndex`** from the assertion, if one was present; - your own **local session id**, and the time you wrote the row. Index it on issuer plus subject plus index, so the lookup on an inbound message is one read rather than a scan of live sessions. That index is the only performance decision here and it matters because the lookup happens on someone else's schedule. ## One human, several rows The relationship is not one to one in either direction, and a design that assumes it is will leave sessions alive. 1. One worker may hold **several of your sessions** — two browsers, a desktop and a tablet — created from **one** identity-provider session, so one index maps to several local ids. 2. The same worker may sign in again after the firm's identity provider has started a **new** session, producing a second index, so one subject maps to several indexes. 3. An assertion may carry **no** index at all, in which case you have a subject-scoped row and nothing finer. So the resolution rule is: if the inbound message names an index, end the sessions carrying that index for that issuer. If it names only a subject, end that subject's sessions for that issuer. Never widen beyond the issuer, because the same-looking identifier at another counterparty is a different person. ## Ending it means deleting your record The teardown is a write to your own session state, and it has to be visible to every replica immediately — otherwise a request already in flight to another instance continues on a session you believe is gone. What ends the session is the server-side record ceasing to be valid. Clearing the browser cookie is a courtesy: the browser that holds it may be closed, may be on a machine that is off, and in a back-channel teardown is not part of the exchange at all. A design that relies on the cookie being cleared has not ended anything. Write an audit entry as you go — which issuer, which index, how many local sessions were ended, and the time. When a firm's security team asks whether a departing worker's access really stopped, that entry is the answer. ## Idempotency is the requirement, not a nicety These messages arrive more than once. Retries, duplicate delivery and an operator clicking twice are all normal. Three cases have to be non-events: | Case | Correct behaviour | |---|---| | The same index arrives a second time | nothing further to do; report the same outcome as the first | | An index you have no row for | nothing to do; treat as done, and do not disclose whether the subject exists | | The session already expired on its own | nothing to do; treat as done | The common bug is treating *nothing found* as an error. It produces failures during perfectly normal retries, and it teaches the sender that something is wrong when nothing is. The implementation that gets this right is a delete-by-predicate that reports how many rows it affected and does not care whether that number was zero. ## What this leaf does not decide Ending **every** session a person holds everywhere, on a leaver event, is a different operation reached from a different trigger. So is the policy about what should happen when somebody leaves the firm. This piece is narrow on purpose: persist the join at login, resolve it on arrival, end those records, be idempotent, and leave an audit trail.

  • A logout message arrives naming a SessionIndex you have no record of. What do you return?
    Treat it as already done. You have nothing to end, so the operation succeeded in the only sense that matters, and reporting a failure turns an ordinary retry into a support ticket. It also matters that the reply does not distinguish an unknown subject from a known one with no live session.
  • Why store the subject identifier exactly as received rather than a normalised form?
    Because the inbound message will name it in the same form the assertion did, and matching requires comparing like with like — format and qualifiers included. Normalising for your own account lookup is fine, but keep the received form on the join row or the comparison silently stops matching after some counterparty changes a detail.
  • The assertion carried no SessionIndex at all. What can you still do on logout?
    Fall back to subject scope: end the sessions you issued for that subject at that issuer. It is blunter than index scope — it may end a session the counterparty did not intend to end — and that is the honest cost of an assertion that gave you nothing finer to key on. Never widen past the issuer.

saying these in an interview costs you the question

  • One identity-provider session always maps to exactly one of our sessions.
  • Clearing the browser cookie is what ends the session.
  • An unrecognised SessionIndex is an error and should fail.
  • Match the subject identifier across every counterparty connection, not just the issuer.
  • Store nothing at login; look the session up by the person's email when logout arrives.