Opaque session handles versus JWTs for a product family
Published August 30, 2026 · SynthoCore Automation Inc.
A family of products that share one sign-in has a small, stubborn problem to solve. When a user logs in on one product and moves to another, something has to travel with them that says, credibly, “this person is signed in, and here is who they are.” There are two broad shapes that something can take, and the choice between them decides more than it first appears — it decides, in particular, whether a logout is real.
The first shape is a self-contained token: a signed statement, most commonly a JSON Web Token (RFC 7519), that carries its own claims. Any product that holds the public key can verify the signature and read the claims — subject, expiry, scopes — without asking anyone. The second shape is an opaque handle: a random, meaningless string that is nothing but a lookup key. It carries no information. To learn anything from it, a product has to hand it back to the server that issued it and ask.
The trade the two shapes make
The self-contained token trades verifiability for locality. It is fast: no network round trip, no shared database, each product verifies in isolation. That is genuinely useful, and for short-lived, low-stakes assertions it is often the right answer. The opaque handle trades locality for control. Every resolution is a question put to the issuer, which is slower and couples the products to a central authority — but it means the issuer is in the loop on every single request, and an issuer in the loop is an issuer that can say no.
The property that decides it: revocation
The decisive question for a product family is not speed. It is what happens the instant a user clicks “log out” — or the instant an administrator disables an account, or a token leaks. You want that event to take effect now, everywhere, not eventually.
A self-contained token does not give you that, and the reason is exactly the property that made it fast. Because each product verifies the token locally, nobody asks the issuer whether it is still good. So the token stays valid until it expires on its own, regardless of what happened to the session behind it. Clearing the client’s copy on logout helps for the honest case, but a token that was copied or captured keeps working until its expiry passes. The usual patches — very short lifetimes with a refresh dance, or a server-side denylist checked on every request — each chip away at the problem, but the denylist is the tell: the moment you consult central state on every request to see whether a token was revoked, you have rebuilt the opaque handle with extra moving parts.
An opaque handle makes revocation a deletion. Invalidate the session record and the next resolution simply fails. There is no window in which a copied string keeps working, because the string never meant anything on its own. Logout means gone. When “logout that actually revokes” is a requirement rather than a nicety — and for a family of products holding real customer work, it is — that property alone is usually enough to settle the design.
Introspection: the server-to-server resolution
The standard way a product server turns an opaque handle into an answer is token introspection, defined in RFC 7662. The product server takes the handle it received and makes an authenticated back-channel call to the authorization server’s introspection endpoint, presenting the token. The response is a small JSON document whose most important field is a single boolean:
The whole game is active. It is authoritative and current: it reflects a revocation the moment the record changes, because it is read from the record. The call happens server to server, over the back channel, authenticated as the product — the browser never sees it and cannot forge it. The cost is one network call per resolution, which is the price of the control you were buying. You pay it down with a short-lived cache keyed to the token, but you keep the cache’s time-to-live small on purpose: the TTL is exactly the window in which a just-revoked session can still be honoured, so you choose it deliberately rather than reaching for a long cache to make a graph look faster.
Carrying the session across one domain
A product family usually lives on one registrable domain, with each product on its own host beneath it. The session travels in a cookie, and getting the cookie’s attributes right is most of the security of the scheme.
To share a session across the products, the cookie is set on the parent domain with a Domain attribute, so the browser sends it to each host in the family. Its SameSite attribute governs when it rides along on requests. SameSite=Lax, the modern default, sends the cookie on top-level navigations within the same site and withholds it from cross-site requests — and because hosts under one registrable domain are the same site, movement between the products is same-site and the cookie behaves. You reach for SameSite=None only when a flow is genuinely cross-site, and that setting demands Secure. Alongside that, the session cookie is always HttpOnly, so page scripts cannot read it, and always Secure, so it rides only over HTTPS.
There is one sharp edge worth naming, because it surprises people who reach for the strongest-looking option. The __Host- cookie name prefix is a strong integrity constraint: a conforming browser will only accept a __Host- cookie if it is Secure, has Path=/, and — the catch — carries no Domain attribute, which locks it to the exact host that set it. That host-only lock is precisely what a family-wide session cookie cannot accept, because sharing across sibling hosts is the entire point and that requires a Domain attribute. So a shared parent-domain session cookie cannot use __Host-; the two requirements are in direct conflict. If you want prefix protection, you step down to __Secure-, which requires Secure but permits Domain, and you lean on Secure, HttpOnly, and SameSite for the rest. That is a real trade, and it is better made on purpose than discovered when a shared login mysteriously fails to stick.
What a product should — and should not — learn
Introspection returns a payload, and the payload is a boundary. A product server should learn from it only what it needs to make the decision in front of it: a stable subject identifier, that the session is active, the expiry, and the coarse authorization facts — the scopes or entitlements — that bear on what this particular product is allowed to do. That is the working set.
What a product should not receive is everything the identity system happens to know. A resolution is not an invitation to ship a full profile to whichever product asked; a peripheral product has no business receiving a customer’s personal record because it resolved a session cookie. Keep the payload to what the product must act on, and let the introspection response enforce that minimum — return subject, status, and relevant scope, not the whole file. Equally, the product should treat the payload as an assertion from the authority, verified as having come from the introspection endpoint, rather than as anything the client supplied: trust what the issuer signed off on, never a claim the browser could have set for itself.
Where that leaves the choice
Stated in the abstract, “token or handle” has no answer. Stated as a requirement, it usually does. If a logout has to be true the moment it happens — across every product a customer touches — then a self-contained token is working against you and an opaque handle resolved by introspection is working for you. The parent-domain cookie carries that handle across the family, the cookie attributes keep it from wandering or being read, and the introspection response keeps each product from learning more about the customer than its job requires. None of it is exotic. It is the same bookkeeping discipline applied to sessions that a good calculation applies to numbers: make the important thing — here, “is this session still valid?” — a question with a current, checkable answer rather than an assumption nobody revisits.
If this is the kind of rigour you want in how your systems are built and reviewed, get in touch.