A GraphQL subscription streams over SSE for hours — how do you handle the credential that authorized it expiring?
answer
- checked once, at the door
- nothing travels upstream to refresh it
- the response outlives the credential
- bound stream life to token life
- stagger the closes, avoid the herd
basics
~20 sThe credential is checked once, on the opening request, and nothing can travel upstream to refresh it. The usual answer is to bound each stream's lifetime to the credential's remaining life, close it with jitter, and let the client reconnect.
solid answer
~50 sAuthorization on this transport happens exactly once: on the ordinary HTTP request that opens the stream. After that the response stays open for hours and there is no upstream channel on which a client could present a refreshed token, so by default a stream silently outlives the credential that authorized it — and outlives a revocation too. Three levers exist. **Bound the stream**: record the credential's expiry when the stream opens and end the response at or before it, letting the client reconnect with a fresh credential. **Re-check per event**: consult a revocation or session store before writing each result, which costs a lookup per event but catches revocation quickly. **Accept the window** and keep it short, which is defensible for low-sensitivity data. Whichever you pick, stagger the closes: expiring thousands of streams on one boundary produces a synchronized reconnect storm.
code
pseudocode · 15 linesopenStream(request):
principal = authenticate(request) # the only check the transport gives you
expiresAt = principal.credentialExpiry
# close before expiry, at a per-stream jittered point, to avoid a synchronized herd
remaining = expiresAt - now()
closeAt = now() + remaining * uniform(0.70, 0.95)
respond(status = 200, contentType = "text/event-stream")
for event in subscribe(request.document, principal):
if now() >= closeAt:
writeEvent(reason = "credential_expiring") # tell the client it was deliberate
break
writeEvent(execute(request.document, event, principal))
endResponse()go deeper
Remember that the credential is checked on the request that opens the stream and not again while it runs, and that nothing can be sent upstream afterwards to update it. That single fact is what everything else follows from.
Explain the concrete remedy: record the credential's expiry when the stream opens, end the response at or before it, and have the client reconnect with a fresh credential. Be able to say why an in-band refresh is impossible here.
Show production judgement — separate expiry from revocation, price the per-event re-check against the stream's event rate, and anticipate the synchronized reconnect a fleet-wide expiry boundary produces if closes are not jittered.
Own the policy across the system. Decide what overrun is acceptable for which data, whether revocation latency is a product requirement or an aspiration, and how the same close-and-reconnect machinery serves deploys and load shedding rather than existing only for credentials.
## Where the check happens, and why that is the whole problem A subscription delivered as `text/event-stream` starts life as a completely ordinary HTTP request. It carries a cookie or an `Authorization` header, it passes through the same authentication middleware as any query, and it is authorized once — at that moment, against the credential presented. That is genuinely the transport's best feature: no in-band handshake to design, no connection payload where credentials have to be smuggled, and every existing rate limiter, audit log and identity library works unchanged. Then the response opens and stays open. On a job-board graph, a recruiter leaves a dashboard up all afternoon; a single `applicationReceived` stream lives for six hours. The access token that authorized it was minted with a 15-minute lifetime. Nothing about the open response knows or cares. And because a server-sent event stream carries nothing upstream, there is no message the client can send to present a newer token — the refresh flow every other request in the application uses simply has nowhere to happen. So the honest statement of the problem: **the authorization decision is a point-in-time snapshot, and the stream is a long-lived grant made on the strength of it.** That is true of any long-lived connection, but a socket subprotocol at least *could* accept a refreshed credential in a client message; here the option does not exist. ## Three levers, and what each is really for **Bound the stream to the credential.** When the request opens, read the credential's expiry and remember it against the open response. When that moment arrives, finish the response body — ideally after writing a final event that tells the client why, so it can distinguish a deliberate close from a network failure. The client obtains a fresh credential and opens a new stream. This is the default answer and the one interviewers expect first, because it keeps the invariant simple: no stream is ever older than the token that authorized it. **Re-check before each event.** Consult a session or revocation store on every result you are about to write. This is the only lever that responds quickly to *revocation* rather than expiry — a recruiter removed from a requisition should stop receiving its applications now, not in eleven minutes. The cost is a lookup per event per subscriber, which is exactly the multiplier you cannot afford on a high-rate stream, so it is usually applied selectively: on sensitive fields, or on a coarse timer rather than every event. **Accept the window, deliberately and briefly.** For low-sensitivity data a bounded overrun is a reasonable trade, but it must be a decision with a number attached, not an accident. "A stream may outlive its credential by at most 90 seconds" is an answer; "we never thought about it" is the thing being tested for. ## The failure mode the levers create Bound-and-reconnect has a sharp edge that only appears at scale. If every stream closes exactly at its credential's expiry, and credentials were minted in bursts — a shift change, a deploy that logged everyone back in, a mobile app waking a fleet at once — the closes synchronize. A tier holding around 8,400 open streams at peak can drop and re-establish a large fraction of them in the same second, and the reconnect is not free: each one re-authenticates, re-validates a document, and re-establishes a source of events. The fix is ordinary but has to be said: close at a jittered point *before* expiry, not at it. Pick uniformly from something like 70-95% of the remaining lifetime per stream, and the same population sheds continuously instead of in a spike. It also removes a subtler bug — a stream closed exactly at expiry races the client's own refresh, so the reconnect can arrive carrying a credential that has just died. ## What good answers add Distinguish **expiry** from **revocation**. Bounding the stream handles expiry perfectly and revocation only by accident, at whatever granularity the bound gives you. If the requirement is "access removed takes effect immediately", only a per-event or timer-driven re-check delivers it, and that cost has to be designed in rather than discovered. Mention that the same close-and-reconnect machinery is what you already need for deploys and for shedding load, so it is not a cost unique to credentials. And note what the reconnect does *not* solve on its own: a new stream starts producing from the moment it is established, so whatever happened during the gap is a separate concern the design has to answer on its own terms. Finally, be clear about status: none of this is specified. No ratified GraphQL document defines this transport, let alone its credential lifecycle, so every one of these choices is yours to make and yours to write down.
- How is this different from a subscription carried on a socket subprotocol?Structurally the same problem — one check, a long-lived grant — but a socket has an upstream direction, so a client can present a refreshed credential in a message and the server can accept it without tearing anything down. Over an event stream that option does not exist, so refresh necessarily means close and reconnect. It is worth saying the underlying authorization question is identical; only the remedy is narrower.
- The requirement is that removing someone's access stops their stream immediately. Which lever meets it?Only a re-check while the stream is running. Bounding the stream to credential expiry bounds the exposure but does not react to revocation, so the worst case is the whole remaining lifetime. Consulting a revocation or session store before writing results — per event on low-rate streams, or on a short timer on high-rate ones — is what turns "eventually" into "within seconds", and the lookup cost has to be budgeted per subscriber rather than per request.
- Why close before the credential expires rather than exactly at expiry?Two reasons. It gives the client a live credential to reconnect with, instead of racing its own refresh with a token that has just died. And drawing each stream's close point from a jittered window spreads the reconnects, so a population of streams whose credentials were minted in a burst sheds continuously rather than reconnecting in one spike that hits authentication and subscription setup at once.
A visitor badge checked once at the door. The door never checks again, so the building has to decide in advance what happens when the badge expires while the visitor is still inside — and to avoid marching everyone out at the same chime.
saying these in an interview costs you the question
- Assumes the server re-checks the credential automatically while streaming
- Proposes the client send a refreshed token on the open stream
- Issues long-lived tokens just to keep the stream alive
- Treats expiry and revocation as the same requirement
- Closes every expiring stream at the same instant
- Says a stream is safe because it was authorized once