logo

Database

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
end

The `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
end

The 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.