3.2 KiB
5. The gateway, and what TokenRelay actually relays
Prev: 4. What is not validated · Next: 6. mTLS
Spring Cloud Gateway Server MVC ships a filter that sounds like it solves the whole problem:
filters:
- TokenRelay=
It is TokenRelayFilterFunctions, with two forms:
public static HandlerFilterFunction<ServerResponse, ServerResponse> tokenRelay();
public static HandlerFilterFunction<ServerResponse, ServerResponse> tokenRelay(String clientRegistrationId);
The documentation is precise about what it does, and it is not what the name suggests to most
readers: it takes the access token of the currently authenticated user — the one obtained
by oauth2Login() — or of the named client registration, and puts it in the Authorization
header of the proxied request.
It does not mean "forward the incoming bearer token". There is nothing to forward from if the gateway never performed a login.
The A/B
gateway.yml defines two routes to the same destination, differing only in the filter.
docs/output/04-gateway-token-relay.txt:
/edge/relay (TokenRelay=) → 200, sub: alice
/norelay/x (no TokenRelay) → 200, sub: alice
Identical. The token arrived because the gateway proxied the Authorization header the caller
sent, which it would have done anyway. In this configuration TokenRelay contributes nothing.
Where it earns its place is the other shape: a gateway that terminates a browser session
with oauth2Login(), keeps the tokens server-side, hands the browser nothing but a session
cookie, and attaches a token on the way through. That is the backend-for-frontend pattern, and
it is a genuinely good answer to "where do I keep the token in a SPA" — because the answer is
"not in the SPA".
The 401 that has nothing to do with OAuth
Before GatewaySecurityConfig existed, every call through the gateway came back:
HTTP/1.1 401
WWW-Authenticate: Basic realm="Realm", charset="UTF-8"
A perfectly valid bearer token, rejected by a gateway that had never been told to expect one.
Spring Boot applies a default filter chain — HTTP Basic and form login over every path — to any
application on the classpath with Spring Security and no SecurityFilterChain bean. A gateway
is an application. WWW-Authenticate: Basic in front of a token-based estate always means this.
Choosing where the gateway sits
| Shape | Gateway does | Downstream sees |
|---|---|---|
| Pass-through | Routes; validates nothing | The caller's token; each service validates it |
| Edge validation | Validates the token, strips it, adds its own identity | The gateway's identity |
| BFF | oauth2Login(), holds tokens, TokenRelay |
The user's token, obtained by the gateway |
Pass-through is the default and is fine while every service validates properly — chapter 4 is about how often that assumption is wrong. Edge validation is the one that tempts teams into "the gateway checked it, so we can trust the header", which is chapter 6's failure mode wearing a different hat.
Prev: 4. What is not validated · Next: 6. mTLS