In an OAuth 2.0 authorization code flow, why is the callback URL the browser lands on treated as a secret?
answer
- the response rides in the address bar
- a query string gets copied everywhere
- logs, history, referring URL, screenshots
- the code is single use, short lived
- redirect off the callback URL immediately
basics
~20 sThe authorization response arrives as query parameters on that URL, and the code in it is a one-time credential redeemable at the token endpoint. Whatever copies the URL - an access log, browser history, a referring header - copies the credential.
solid answer
~50 sIn the authorization code flow the authorization server answers with a redirect to the client's registered redirection endpoint, and the response rides in that URL's query string: `code` plus the `state` the client sent. The `code` is a short-lived, single-use credential that the client redeems at the token endpoint, so for the seconds it is alive the URL is as sensitive as the tokens it buys. The problem is that URLs are the most-copied thing in a web stack: the redirection endpoint's access log records the full request line, the browser keeps it in history, an outbound request from the callback page can expose it as the referring URL depending on the referrer policy in force, and a user reporting "the page that broke" pastes the code into the ticket. The practical rule is to redeem the code immediately, redirect straight off the callback URL to a clean path, and keep query strings out of the logs on that one endpoint.
code
http · 5 linesHTTP/1.1 302 Found
Location: https://claims.example.rail/oauth/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj
GET /oauth/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj HTTP/1.1
Host: claims.example.railgo deeper
Remember what is in the callback URL: an authorization code and the state value, not just routing information. Say out loud that the code is a credential and the URL therefore is too.
Explain the mechanics of each escape path - access log, history, referring URL - and why transport encryption addresses none of them, then name single use and short lifetime as the protocol's own bound.
Show the operational fix you would actually land: query-string exclusion scoped to the callback route, immediate redemption, a redirect to a clean path, and an alert when a code arrives twice.
Frame it as a copy-count problem across the estate: every service that logs request lines multiplies the exposure, so the standard you set for callback routes matters more than any single integration's care.
## What the authorization response actually is In the authorization code flow, the client sends the resource owner's browser to the authorization server's authorization endpoint. After the resource owner authenticates and consents, the authorization server does not reply to the client directly. It replies to the **browser** with a redirect, and the browser then makes a fresh request to the client's registered **redirection endpoint**. The response parameters travel in the query string of that redirect: ```http HTTP/1.1 302 Found Location: https://claims.example.rail/oauth/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=af0ifjsldkj ``` That is the whole authorization response. `code` is a credential: whoever presents it to the token endpoint, within its lifetime and before it is redeemed, is asking for tokens with it. It is not a session identifier, not a tracking parameter and not routing data. The usual objection is "but the connection is encrypted". Transport security protects the URL **in flight between two endpoints**. It does nothing about the copies that both endpoints and the browser make of it once it has arrived, and those copies are the whole problem. ## Where a URL gets copied For a regional rail operator's delay-repay claim service - the client - reading a passenger's journey history from a ticket retailer's API, the same code ends up in several places within one request: - the **redirection endpoint's access log**, which records the full request line including the query string, and which is usually shipped to a log aggregator that many more people can read than can read the application's database; - the **browser's history**, which may be synchronised to other devices and is readable by anything with access to the profile; - the **referring URL** sent on requests the callback page itself makes, if that page loads or navigates to anything off-site - how much of the URL is sent depends on the referrer policy in force; - **screenshots, support tickets and bug reports**, because the URL is visible in the address bar and users copy it; - **intermediaries that log request lines**, such as a gateway or reverse proxy in front of the client. | Escape path | What it copies | What bounds it | |---|---|---| | Access log at the redirection endpoint | The full query string, persisted | Strip or exclude query parameters on that route | | Browser history and address bar | The full URL, kept after the session | Redirect off the callback URL at once | | Referring URL on an outbound request | As much of the URL as the referrer policy allows | Keep third-party content off the callback page | | A pasted link in a ticket | Everything the user can see | Short lifetime and single use | ## What the specification relies on instead The protocol does not assume the URL stays private. It bounds the damage two ways. First, an authorization code is **single use**: once redeemed, a second presentation must be refused, and a code presented twice is a signal worth alarming on. Second, it is **short-lived** - a code is meant to live seconds, not hours. Beyond that, redeeming a code is a back-channel request to the token endpoint, and a confidential client must authenticate there, so a leaked code alone does not always buy tokens. Those are mitigations, not a licence. Within the live window a leaked code is redeemable, and "it expires in ten minutes" is a ten-minute window an attacker who is reading your logs in real time does not need. ## What to do at the redirection endpoint 1. **Redeem the code immediately** on receipt and never store it. 2. **Redirect off the callback URL** to a clean path as soon as the exchange is done, so the URL with the code does not stay in the address bar or become the referring URL of anything the user does next. 3. **Exclude the query string from logs on that route specifically**, rather than trusting a global redaction rule to know which parameter names matter. 4. **Keep off-site content off that page**: the callback should do its work and redirect, not render a dashboard full of third-party resources. 5. **Treat a code that arrives twice as an event**, not as a retry to be quietly tolerated. The habit to build is simple: the callback URL is a credential in transit, and a credential in transit should exist for as short a time, in as few copies, as you can manage.
- If the authorization code is that exposed, why does the protocol put it in a URL at all?Because the authorization server has no channel to the client at that moment - only the resource owner's browser. The redirect is the only way back. The design accepts a visible, short-lived handle in the front channel precisely so that the valuable exchange happens on a back-channel request the browser never sees.
- What should the redirection endpoint do if the same authorization code arrives a second time?Refuse it and treat it as a security event. The token endpoint will reject the redemption anyway, since a code is single use, but a repeat arriving at the client usually means the URL was replayed from a log, a history entry or a shared link, and that is worth investigating rather than retrying.
- Does answering the callback with a redirect to a clean path remove the code from the browser's history?It stops the code URL becoming the page the user is sitting on, and so stops it becoming the referring URL of their next action, but a navigation the browser already recorded stays recorded. It reduces exposure; it does not erase it, which is why short lifetime and single use still carry the weight.
saying these in an interview costs you the question
- Thinks the authorization code is harmless because it is not a token
- Says transport encryption makes logging the full callback URL safe
- Logs complete request URLs at the redirection endpoint by default
- Assumes the browser never exposes the callback URL anywhere else
- Treats a ten-minute expiry as making a code in history harmless
- Stores the code in the user's session for later use