A URL is convenient because it is easy to copy, bookmark, route, log, and inspect. Those same properties make it a poor place for passwords, long-lived access tokens, recovery secrets, or other values that should remain confidential.
The problem is not that HTTPS exposes the URL to everyone on the network. HTTPS protects the request in transit between endpoints under its security assumptions. The problem is what happens before and after transport: URLs routinely pass through browser history, application and proxy logging, monitoring systems, support messages, screenshots, and copied links. A secret placed in a URL can therefore reach systems and people that never needed the secret.
The useful mental model is: a URL is an identifier and navigation input, not a secret container. This article explains why that boundary matters, how to design flows that keep sensitive values out of URLs when possible, and what to do when a short-lived secret must temporarily appear in one.
Start with the data flow, not the address bar
Consider an application that accepts an API token like this:
https://api.example.test/report?access_token=<secret>The application may validate the token correctly. The connection may use HTTPS. The token may never be shown in the page body.
Yet the full request target can still travel through several components inside the service architecture. A reverse proxy may record it. The application framework may include it in access logs. An observability product may collect those logs. A developer may paste the failing URL into an issue while debugging.
The security failure is a confidentiality boundary mismatch. The token is supposed to be known only by components that authenticate the request, but the URL is handled by components whose normal job includes recording or reproducing request identifiers.
Keeping secrets out of URLs reduces accidental propagation into those secondary systems. It does not protect a secret that is already stolen from the client, stop malware on the user’s device, repair an authorization flaw, or make an untrusted endpoint trustworthy.
Why URLs spread farther than request bodies
A request contains several kinds of data. They do not all receive the same operational treatment.
For a simplified HTTPS request:
POST /session HTTP/1.1
Host: app.example.test
Content-Type: application/x-www-form-urlencoded
username=sam&password=<secret>many access-log configurations record the method, host, path, status, timing, and similar request metadata without recording the request body. That separation is useful because the body can contain credentials or personal data.
Now move the password into the query string:
POST /session?username=sam&password=<secret> HTTP/1.1
Host: app.example.testThe credential has crossed from body data into the request target. Infrastructure that intentionally avoids body logging may still record the request target as ordinary request metadata.
This does not mean request bodies are inherently confidential. Applications, tracing middleware, debugging tools, and proxies can log bodies too. The point is narrower: putting a secret in a URL exposes it to an additional class of routine handling that is difficult to control consistently.
Put credentials in channels designed to carry them
The first defensive decision is to choose a request location that matches the value’s purpose.
For an API credential, an authorization mechanism carried in an appropriate request header is generally preferable to a query parameter when the protocol supports it. For a password submitted by a web form, send it in the HTTPS request body rather than encoding it into the URL. The exact header, body format, and authentication scheme depend on the protocol and application.
This changes the data flow:
URL
-> identifies resource and operation
credential
-> carried separately
-> consumed by authentication logicThe separation is valuable because infrastructure can apply different handling rules to each class of data. A proxy can log paths while suppressing sensitive headers. An application can avoid request-body capture on authentication routes. A monitoring pipeline can redact known credential fields.
None of those controls is automatic. You still need to configure logging and telemetry deliberately. Separating secrets from URLs makes that job tractable instead of forcing every URL consumer to understand which substring is confidential.
Some security flows legitimately begin with a secret link
Password-reset links, email-verification links, and invitation links create an important exception. The user must receive something they can click, so a one-time or short-lived credential may need to appear in the URL.
The correct lesson is not “a secret can never appear in a URL.” It is to treat such a URL as a temporary credential with a deliberately small exposure window.
Suppose an account-recovery email contains:
https://app.example.test/recover?t=<random-recovery-token>Anyone who obtains a still-valid token may be able to exercise the recovery capability, depending on the rest of the flow. The application should therefore design the token around that risk: give it one narrow purpose, a limited lifetime, and a server-side state transition that makes it unusable after successful consumption when the design permits one-time use.
A useful pattern is to consume the URL token before the user begins interacting with unrelated application pages. After validation, the server can establish a restricted recovery state and redirect the browser to a clean URL such as:
/recover/set-passwordThe sensitive token no longer needs to remain in subsequent navigation. The restricted state should authorize only the recovery operation it was created for, not become a general authenticated session unless the application’s recovery design explicitly and safely makes that transition.
This pattern reduces how long the token remains visible in the browser’s current URL and how often it is copied into later requests or diagnostic material. It cannot erase copies that were already created before consumption.
Treat URL fragments as a different mechanism, not a universal fix
A URL can contain a fragment after #:
https://app.example.test/callback#valueFor normal HTTP navigation, the browser does not send the fragment as part of the HTTP request target to the server. That property can be useful in protocols specifically designed around browser-side fragment processing.
It does not make fragments a general-purpose secret store. Client-side code can read the fragment, browser extensions or compromised page code may have access to browser-visible data, users can still copy the complete URL, and the application may accidentally persist or transmit the value later.
Do not move a query-string secret behind # and assume the problem is solved. Use the flow defined by the protocol you are implementing, and minimize how long any browser-visible credential exists.
Redaction is a containment control, not the primary design
Sometimes an application already accepts sensitive query parameters for compatibility reasons. Log redaction can reduce the damage while the interface is being changed.
For example, a logging layer might transform:
/download?document=42&temporary_key=<secret>into:
/download?document=42&temporary_key=[REDACTED]Redaction helps only at the component where it runs. If a load balancer logs the original request before the application redacts it, that earlier copy still contains the value. If an error-reporting agent captures the raw URL independently, application access-log redaction does not affect it.
That leads to an important operational rule: redact as close to collection as possible, but remove the sensitive value from the URL at the design boundary whenever you can.
When a legacy interface cannot change immediately, map the complete request path through edge proxies, web servers, application middleware, tracing, metrics labels, analytics, error reporting, and support tooling. Configure each collector to omit or redact the sensitive field. Then test the actual emitted telemetry rather than assuming the configuration works.
Avoid placing unbounded or secret-bearing URLs into metric labels. Apart from confidentiality, high-cardinality labels can create operational cost and make monitoring harder to use.
Referrer controls reduce one path of disclosure
Browser navigation creates another possible propagation path: referrer information. A browser can send a Referer request header when navigating or loading resources, subject to the active referrer policy and browser rules.
A suitable Referrer-Policy can reduce how much URL information is sent to other origins or requests. That is useful defense in depth, especially for pages reached through sensitive links.
It is not a substitute for keeping secrets out of URLs. Referrer policy does not remove the value from server access logs, browser history, copied links, screenshots, monitoring systems, or telemetry that already captured the URL. Treat it as control over one disclosure path, not as permission to place credentials in query strings.
Logging needs its own trust boundary
Moving a secret out of a URL does not finish the work. A system can still recreate the same problem by logging sensitive headers or request bodies indiscriminately.
Define what each telemetry stream actually needs. An access log may need the route, status code, request identifier, and latency, but not an authorization credential. An authentication event may need an account identifier and outcome, but not the submitted password. A debugging trace may need selected request fields, but not every header and body byte.
This is data minimization applied to observability: collect enough to operate and investigate the service, but avoid collecting values whose disclosure would grant access or expose unnecessary sensitive data.
Be especially careful with temporary debugging. Enabling full request capture during an incident can quietly bypass the normal redaction policy. If deeper capture is genuinely necessary, scope it narrowly, restrict access, set a short retention period, and verify that it is disabled afterward.
Test the places where secrets could escape
A design review can identify likely propagation paths, but verification should use the running system.
Create a test credential that is clearly fake and has no production authority. Exercise the relevant flow in a non-production environment that mirrors the important logging and proxy layers. Then search the resulting access logs, application logs, traces, error events, and analytics records for the marker.
The test answers a concrete question: did a value intended only for authentication appear somewhere else?
For browser flows, also inspect the URL after redirects and navigation. A recovery token that remains in the address bar across several pages has a larger accidental-exposure surface than one that is consumed and replaced with a clean URL.
Do not use a real secret as a telemetry canary. The verification method should not create the exposure it is meant to detect.
Common fixes that leave the main risk intact
Encoding a value does not make it confidential. Percent-encoding changes how characters are represented in a URL; Base64 changes representation as well. Anyone who receives the encoded value can generally recover the original representation. Use encoding for syntax and transport requirements, not secrecy.
HTTPS is necessary for protecting sensitive web traffic in transit, but it does not control what endpoints and intermediaries inside your own architecture record after they receive the request. “We use HTTPS” therefore does not answer the logging and propagation problem.
Short retention helps limit how long an accidental log copy remains available, but a credential can still be abused while it is valid. Redaction, access control, retention, and encryption of logs are useful layers. They do not provide the same guarantee as never collecting the credential there.
Finally, replacing a sensitive query parameter with a sensitive path segment does not solve the design problem:
/download/<secret>Paths are also routinely handled as URL data. The important boundary is between secret-bearing data and the URL itself, not between different parts of the URL path and query.
Choose the control based on the kind of value
For ordinary credentials such as passwords and reusable API tokens, keep them out of URLs from the start. Carry them through the authentication mechanism intended by the protocol, and configure telemetry not to capture them.
For capability-style links such as account recovery or invitations, accept that the URL temporarily carries authority and design around that fact. Make the credential narrowly scoped, time-limited, and invalidatable. Consume it promptly, move the user to a clean URL, and apply referrer and logging controls as defense in depth.
For legacy interfaces that already place secrets in URLs, prioritize changing the interface. Until migration is complete, redact at every collection layer you control, restrict telemetry access, keep retention appropriate to operational needs, and test for leakage with non-sensitive markers.
These controls reduce accidental exposure. They do not compensate for predictable tokens, weak authorization, compromised clients, or a server that sends credentials to an untrusted destination. Those are separate security problems and need separate controls.
Conclusion
URLs are designed to travel. They are copied, logged, inspected, and reused because those behaviors make the web and HTTP systems practical. Sensitive credentials have the opposite requirement: they should reach as few places as possible.
Design around that mismatch. Keep reusable secrets out of URLs, separate authentication data from resource identifiers, minimize sensitive telemetry, and treat unavoidable secret links as short-lived capabilities that should be consumed and retired quickly.
The practical test is simple: if exposing the complete URL to an ordinary logging or support system would expose authority that system does not need, the sensitive value belongs somewhere else.