Sensitive information sent via URL parameters
Need
Transmission of tokens in request headers instead of URLs
Context
• Usage of Elixir 1.15+ for building application services
• Usage of the Req HTTP client for outbound requests
Description
1. Non compliant code
defmodule MyApp.Partner do
def fetch_profile(token) do
Req.get!("https://api.partner.example/profile?access_token=#{token}")
end
endThe `fetch_profile/1` function below calls a partner API with `Req.get!/1` and puts the access token in the query string of the URL. URLs are not treated as secrets. They are written to the access logs of the partner, of every proxy and load balancer on the way, and of the monitoring tools that trace outbound requests, and they often appear in error messages and crash reports. Anyone with access to any of those logs obtains a working token. Tokens and passwords sent as query parameters from the browser are also stored in the history and leaked to third parties through the `Referer` header.
2. Steps
• Send tokens, API keys and passwords in the `Authorization` header or in the request body, never in the URL of `Req`, `Finch`, `HTTPoison` or `Tesla` requests.
• Receive credentials from browsers in `POST` bodies or `HttpOnly` cookies instead of query parameters.
• Make tokens that must appear in links single-use and short-lived.
• Configure access logs and tracing to redact query strings on endpoints that historically accepted secrets.
• Rotate any credential that was sent in a URL, since it may be stored in logs.
3. Secure code example
defmodule MyApp.Partner do
def fetch_profile(token) do
Req.get!("https://api.partner.example/profile", auth: {:bearer, token})
end
endThe corrected function sends the token with the `auth: {:bearer, token}` option of Req, which puts it in the `Authorization` header, and keeps the URL free of secrets. Headers are encrypted by HTTPS like the rest of the request, but access logs, proxies and tracing tools do not record them by default, and they never reach browser history or `Referer` headers. Parameters that are not secret, such as pagination or filters, can stay in the query string, passed with the `params` option so Req encodes them correctly.