skip to content

Your relying party's redirect_uri must cover local, preview and production — how do you manage that set, and where does the post-login deep link ride?

level: middleimportance: should knowfreq 46%

answer

  1. one route, several public URLs
  2. registered and compared as a fixed string
  3. build it from configuration
  4. TLS terminated in front of you
  5. the deep link rides in the record

basics

~20 s

Register one callback URL per environment, preferably under a separate client registration each, and build the value from configuration rather than from the inbound request. The post-login deep link rides inside the server-side login record, never appended to the redirect_uri.

solid answer

~40 s

The callback is one route with a different public URL per deployment, and the provider compares the `redirect_uri` you send against what was registered. Prefer a registration per environment over one registration carrying every environment's callback: a client secret on a laptop then authenticates as a non-production client, not as production. Build the value from configuration, not by reconstructing the URL from the request — behind a TLS-terminating proxy that reconstruction yields `http://` and fails against a registered `https://` value. Send the identical string again at the token request; a trailing slash or an added port makes it a different value. The destination a user was heading to is application state: keep it in the parked login record, validated as a path on your own origin when read.

code

http · 8 lines
http
POST /token HTTP/1.1
Host: idp.fleet-operator.example
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Ffleet.example.com%2Fauth%2Fcallback
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

go deeper

for a junior

Know that the callback URL is configuration that differs per deployment, and that the value sent on an authorization request has to be one the provider already has on file for that client.

for a middle

Explain why reconstructing the URL from the inbound request breaks behind a TLS-terminating proxy, and why the identical string has to appear again on the token request.

for a senior

Argue for a registration per environment on the reach of a leaked secret, not on tidiness, and have a real answer for ephemeral preview hosts that does not ask for a wildcard.

for a principal

Treat the registered set as a change surface with an owner: who may add a callback, what a non-production secret reaches, and what the provider's tenancy model forces you to share.

## The same code runs in four places and only one of them is production The dashboard's callback is a single route, but the URL that reaches it differs per deployment: a `localhost` address on a laptop, a per-branch preview host, a staging host, and `https://fleet.example.com/auth/callback` in production. The provider compares the `redirect_uri` on an authorization request against what was registered for that client, as a fixed value. Why the comparison is exact is the specification's argument; what it leaves a server author is an operational problem — a registered set that has to cover every place the code runs, and that has to change whenever a deployment target does. ## One registration per environment beats one registration with a long list | Approach | Reach of a leaked client secret | Who changes the list | Fit | |---|---|---|---| | One registration holding every environment's callback | A non-production secret is a production secret | Anyone with provider administration | One environment, or a very small team | | One registration per environment, one or two callbacks each | Contained to that environment | The owner of that environment | The default worth arguing for | The argument that wins the second is not tidiness. With a single registration, a client secret sitting in a developer's `.env` file authenticates as the same client as production, and the registered list has to include a `localhost` callback that production's own registration would never carry. Separate registrations also let the non-production ones point at a non-production tenant of the provider, populated with test staff rather than real depot supervisors. ## Build the value from configuration, not from the inbound request The most common way this fails in production has nothing to do with the provider's rules. A relying party that constructs its own callback URL from the request it is handling — taking scheme, host and port from what arrived — gets the scheme wrong the moment a TLS-terminating reverse proxy sits in front: the proxy speaks HTTPS outward and plain HTTP inward, so the application builds `http://fleet.example.com/auth/callback` and the provider rejects it against the registered `https://` value. It works on a laptop, where nothing terminates TLS, and breaks on the first deployment behind a proxy. Two fixes, and they are not equivalent: 1. **Put the callback URL in configuration** — one value per environment — and use exactly that string in the authorization request and again in the token request. This is the answer. 2. **Teach the application to trust the proxy's forwarded headers** (`X-Forwarded-Proto` and its companions) so the reconstructed URL comes out right. It works, but it makes the callback URL depend on a header, and therefore on whether the proxy in front is one you control. The same value has to be presented at the token request, byte for byte. A trailing slash, a capitalised host, an added default port — each is a different string, and each produces a failed exchange whose error reads like a credential problem, which sends people looking at the client secret instead of at the URL. ## Preview deployments are the case with no clean answer Ephemeral per-branch hosts cannot be pre-registered, because each is a name nobody knew yesterday. Every option is a compromise: - Route every preview build through **one stable preview hostname** that is registered once, and let the parked login record carry which build the user came from. - Give previews **their own long-lived registration** at a non-production tenant, listing a handful of stable hosts that previews share. - Do not federate in previews at all and run a **local stub issuer** instead — a legitimate choice, whose mechanics belong with testing federated integrations rather than here. What does not work is asking for a wildcard host in the registered value. Most providers do not offer one, and a provider that does is offering to weaken the comparison your entire callback path rests on. ## Where the post-login deep link rides A supervisor opens a shared link to one vehicle, is not signed in, and should land on that vehicle after authenticating. That destination is ordinary application state, and the registration deliberately does not carry it: - **Not appended to the `redirect_uri`.** The registered value is compared as a fixed string, so adding a destination parameter either fails that comparison or forces you to register a variant per destination. - **Not carried as the `state` parameter's payload.** `state` is the key to your server-side record. Making it a bag of application data gives you either a parameter the browser can rewrite — so whoever crafts the link decides where your users land once they are authenticated — or a signed blob that has outgrown what a request parameter should hold. - **In the parked login record**, written when the authorization request is built and read at the callback. Validate it on the way out, not only on the way in: accept a path on your own origin, reject anything carrying a scheme, reject a value beginning with a double slash, re-check after decoding, and fall back to a default landing page rather than failing the login. Nothing in the registered callback set says anything about this final redirect, so nothing but your own check governs it.

  • Why not register one wildcard callback host and stop maintaining the set?
    Most providers do not offer it, and one that does is offering to weaken the comparison your callback depends on — a pattern covers hosts you do not individually control. Why exact matching matters is the specification's argument; operationally, the maintenance a wildcard saves is a few minutes per environment, and what it gives up is the registration's entire value.
  • A deep link points at a depot this supervisor may not see. Where is that caught?
    Not here. The parked record carries a destination and the callback validates it as a local path on your own origin; whether this subject may view that depot is an authorization check the destination route runs after a session exists. Keep the two apart — a well-formed local path is not a permitted one, and conflating them puts an access-control decision in the login code.
  • Which value must the token request repeat, and what goes wrong if it differs?
    The same `redirect_uri` string that went out on the authorization request, byte for byte. A trailing slash, a different case in the host name or an added default port makes it a different value and the exchange fails — usually with an error that reads like a credential problem, which sends people to check the client secret rather than the URL.

saying these in an interview costs you the question

  • Reconstructs the callback URL from the inbound request
  • Expects the provider to accept a wildcard callback host
  • Shares one registration and its secret across all environments
  • Appends the post-login destination to the redirect_uri
  • Redirects to an unvalidated destination parameter after login
  • Assumes a trailing slash difference is harmless