beginner 3 min answer Multiple choice

A mobile client sends its OAuth access token as `?access_token=...` because an intermediate proxy strips the Authorization header. Six weeks later the live token values appear in a CDN edge log that 60 engineers can query and in a partner's referrer report. Which change actually closes this?

oauthbearer-tokenloggingtoken-leakagebeginner
Pick one
Show the full answer Hide the answer

What is being tested

Whether you know what a bearer token actually proves. A bearer token proves possession and nothing else. There is no signature over the request, no binding to the client, no binding to the channel. Whoever holds the string is the user, for as long as the token lives. That makes it a password with an expiry date, and it has to be handled like one.

The mechanism

A URL is not a private channel. It is the one part of an HTTP request that every layer logs by default: the web server access log, the load balancer log, the CDN edge log, the forward proxy, the browser history, and the Referer header sent to any third-party asset on the page. None of those systems were built to hold credentials, so none of them redact, encrypt or restrict them. RFC 6750 (2012) says exactly this about the URI query parameter and tells you to use the header instead.

The damage is not one leaked token, it is a standing list of them. A 60-minute access token is replayable for 60 minutes after someone reads it, but a log with 90-day retention is a queryable store of thousands of credentials with no access review attached. The partner referrer report is worse: the token left your trust boundary entirely and you cannot tell who read it.

Why the other options fail

  • Shorten the lifetime and scrub on ingest. Both are good hygiene and neither closes the hole. Scrubbing is a denylist applied at one of the five or six places the URL lands, so it is wrong by construction the moment someone adds a CDN. A five-minute token still gives a reader of a live log stream five minutes, and shortening the lifetime multiplies refresh traffic against the token endpoint by twelve.
  • Encrypt the value before putting it in the URL. The encrypted string is now itself the bearer credential, because the resource server has to accept it. You have renamed the token, not protected it.
  • Switch to a signed JWT in the same parameter. The signature proves who issued the token, not who is presenting it. A stolen JWT works exactly as well as a stolen opaque token, and it is usually larger, so you have made the log entries longer.

When this is the wrong answer

Sometimes the channel genuinely cannot carry a header: an <img> tag, a native download handler, a link pasted into an email. The answer there is not the session access token in a query parameter. Prefer the header unless the channel cannot carry one, and when it cannot, change what is in the URL rather than where the logs go. It is a separate single-purpose capability URL, signed by the server, scoped to one object, valid for minutes, and ideally single-use. Pre-signed object-storage URLs are this pattern, and they are safe for the same reason the access token is not: the thing in the URL grants one narrow action rather than the user's whole session. If the proxy stripping the header is a vendor appliance you cannot change, a token in a header named something else is still better than a token in the URL, because the header is not in the access-log format.