skip to content

Authorization Across Subgraphs

Carrying the caller's identity from the router to every subgraph, deciding who enforces, and why a subgraph cannot trust the headers it is handed. Interviewers probe that trust boundary.

part ofGraphQLoverview, primer and where to startread it →
on this pageshow

questions

4

In a federated GraphQL graph, how does a caller's identity reach each subgraph?

level: middleimportance: must knowfreq 62%

answer

  1. Who actually talks to the subgraph?
  2. One request in, many requests out
  3. Headers are copied, not proxied
  4. Explicit allowlist on every outbound fetch
  5. Each subgraph verifies for itself

basics

~20 s

A federated router does not forward the client's HTTP request; it issues a fresh request per subgraph fetch. Identity travels only if the router is configured to copy the credential onto each one, and every subgraph must verify it itself.

solid answer

~50 s

The router is a client of the subgraphs, not a proxy. It receives one HTTP request from the caller, plans the operation, then issues its own HTTP request per fetch step, carrying a router-generated document and whatever headers it was configured to attach. Nothing about this is specified: the GraphQL specification defines no authentication, GraphQL over HTTP defines transport rather than identity, and Apollo Federation as a composition specification says nothing about credentials — propagation is router configuration. Three shapes are common: forward the caller's token verbatim and let each subgraph verify it; verify once at the edge and send derived claims as headers, which trusts the network; or mint a short-lived, per-subgraph credential, which bounds blast radius at the cost of a signing path. Credentials belong in headers, never in a variable, and each subgraph should verify and fail closed.

code

pseudocode · 12 lines
pseudocode
// One inbound client request becomes N outbound subgraph requests.
on subgraph_fetch(subgraph, planned_document, variables):
    outbound = new HttpRequest(url = subgraph.url, method = "POST")
    outbound.setHeader("content-type", "application/json")

    // Deny by default: copy only what is explicitly listed.
    for name in ["authorization", "x-request-id"]:
        if inbound.hasHeader(name):
            outbound.setHeader(name, inbound.getHeader(name))

    outbound.body = { "query": planned_document, "variables": variables }
    return send(outbound)

go deeper

for a junior

Recall the basic shape: the caller authenticates once against the router, and the router then makes separate HTTP calls to the services behind it. Identity is carried in headers on those calls, never inside the query document.

for a middle

Be ready to explain that the router is a client rather than a proxy, that header propagation is configuration and not a specified rule, and that the credential is presented once per fetch step in the query plan.

for a senior

Show judgement about which propagation shape you would run and why: pass-through versus derived claims versus a per-subgraph minted credential, with the trust and blast-radius consequences of each stated plainly.

for a principal

Own the consequence that the router is now security-critical infrastructure. Be able to argue token audience strategy, key caching and rotation across many services, and what a propagation misconfiguration would cost the organisation.

## The router is a client, not a proxy A federated request has two hops that people routinely collapse into one. The client sends **one** HTTP request to the router, carrying whatever credential the caller holds — usually an `Authorization` header. The router parses the operation, validates it against the composed supergraph schema, builds a query plan, and then makes **its own** HTTP requests, one per fetch step in that plan, to the subgraphs the plan touches. Those outbound requests carry documents the *router* generated, and they carry exactly the headers the router chose to put on them. Nothing is proxied. The client's request object never leaves the router's memory. So the honest answer to "how does identity reach a subgraph" is: *because somebody configured the router to copy it there.* Left at defaults, a subgraph sees an unauthenticated request arriving from another service and has no idea who is asking. ## Nothing specifies this, and you should say so Candidates reach for "the spec says". No spec says. The GraphQL specification is transport-agnostic and defines no authentication or authorization at all — there is no credential slot in a document, no authentication error class, nothing. The GraphQL over HTTP specification describes how one operation and its response travel over HTTP: parameters, media types, status codes. It does not define who the caller is. Apollo Federation, as a composition specification, defines subgraph SDL, `_service`, `_entities` and how a supergraph is composed; it does not define how a router authenticates a caller or what it forwards downstream. **Credential propagation is router configuration and house convention, not a specified rule.** Two organisations will do it two different ways and both are conformant. ## The three shapes propagation takes **Forward the caller's credential verbatim.** The router copies the `Authorization` header onto every outbound fetch, and each subgraph verifies the token itself against the issuer's keys. This is the simplest thing to reason about, and the subgraph's trust is cryptographic rather than social. The cost is that one token is valid at every subgraph: a compromised subgraph holds a credential that works everywhere, and the token's audience has to be the whole graph rather than one service. **Verify once at the edge, forward derived claims.** The router verifies the token and sends plain headers such as `x-caller-id` and `x-caller-scopes`. Cheap for subgraphs and pleasant to work with — and entirely dependent on nothing else being able to reach the subgraph, and on the router stripping any copy of those headers the caller sent. That is a trust decision with teeth, not a configuration detail. **Mint a fresh credential per subgraph.** The router exchanges the caller's credential for a short-lived assertion scoped to each downstream service. Best blast radius and the clearest audit story, at the price of real machinery: the router becomes a signing path the platform team now operates and must keep available. ## Fan-out means the credential is presented many times One client operation that touches donors, campaigns and donations may produce five or six subgraph fetches, several of them in parallel. The credential is presented and checked once **per fetch**, not once per client request, so verification cost is per hop — cache the issuer's keys rather than fetching them per request. Expiry is per hop too: a token seconds from expiry can pass the first fetch and fail the third, producing a half-answered response for a reason the client cannot see from the outside. ## The credential travels in headers, not in the document There is no field, argument or directive in GraphQL for credentials. Putting a token into a variable is a genuine anti-pattern: variables are logged alongside the operation, they end up inside anything that records or hashes documents, and no server treats them as identity anyway. The transport carries who you are; the document carries what you want. ## A worked shape A charity donations graph runs nine subgraphs — donors, donations, campaigns, receipts, gift aid and so on — behind one router operated by a 4-person platform team. A supporter opens their giving history. The router receives one request with the supporter's bearer token, plans three fetches, and issues three outbound requests, each carrying a router-generated document and the `Authorization` header copied by an explicit allowlist. The donations subgraph sees a request that would return 41 donation records; whether it may answer is decided entirely by what it does with that header, not by the fact that the router sent it. ## What the subgraph must do with what it receives Two rules. **Verify, do not assume** — if the credential is a signed token, check signature, expiry and intended audience inside the subgraph, because "it came from the router" is only as strong as the network's guarantee that nothing else can reach you. **Fail closed** — an absent or unparseable credential produces an anonymous caller, never a privileged one. And remember that the subgraph is entered through more than one door: the router calls `_entities` for entities another subgraph referenced, so a check that lives only in a root-field resolver is not on that path at all.

  • What are you really trusting if the router verifies once and forwards only derived claims as headers?
    The network. Those headers carry no proof, so the subgraph is trusting that nothing except the router can reach it and that the router strips any copy the caller sent. It is cheap and pleasant, and it turns a single ingress or header-forwarding mistake into a full authorization bypass. Forwarding a signed credential, or minting one per subgraph, replaces that trust with a signature check.
  • Why would you mint a fresh per-subgraph credential instead of passing the caller's token through?
    Blast radius and audience. A pass-through token is valid at every subgraph, so one compromised service holds a credential that works everywhere, and its audience must name the whole graph. A router-minted assertion is short-lived and names one subgraph, so it cannot be replayed sideways. The cost is that the router becomes a signing path the platform team has to operate and keep available.
  • One client operation produced six subgraph fetches. How many times is the credential presented and checked?
    Six — once per fetch, not once per client request. That makes verification a per-hop cost, so issuer keys must be cached rather than fetched per request. Expiry is per hop too: a token close to expiry can pass an early fetch and fail a later one, leaving a partially answered response with no obvious cause from the client's side.

saying these in an interview costs you the question

  • Says the router proxies the client's HTTP request to subgraphs
  • Claims the GraphQL specification defines credential forwarding
  • Puts the caller's token in a GraphQL variable instead of a header
  • Assumes a subgraph need not verify because the router already did
  • Thinks each subgraph receives the client's original document

context

open as a page

Why can a subgraph's _entities field bypass the authorization it enforces on root fields?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Because the router never calls the subgraph's own root fields for an entity another subgraph referenced. It calls _entities with key representations, so a check written inside a root-field resolver is simply not on that path.

open as a page

A federated subgraph trusts an x-user-id header its router sets. Why is that unsafe?

level: seniorimportance: should knowfreq 43%

basics

~20 s

The header is unverified text, so the subgraph is trusting whatever can open a connection to it. It is safe only if nothing but the router can reach the subgraph and the router strips any copy the caller sent.

open as a page

Where should authorization live in a federated graph — the router, each subgraph, or the services behind them?

level: principalimportance: should knowfreq 36%

basics

~20 s

Split it by what each layer can know. A router has the credential, the document and the schema but no data, so it can only enforce coarse rules. Per-record rules must live in the subgraph or below.

open as a page