Skip to content

API: accept key in Authorization/X-API-Key header (avoid query-string credential leaks) #1282

Description

@BoredManCodes

Problem

api/v1/validate_api_key.php only accepts the API key as ?api_key= (query string) or in the POST body. Query-string credentials leak through several channels that header-based credentials don't:

  • Web-server access logs — nginx/Apache log the full request URI by default, so GET /api/v1/tickets/read.php?api_key=<secret> lands in /var/log/....
  • Referer headers — any outbound link from a page loaded with ?api_key= in the URL leaks the key to the destination.
  • Browser history / bookmarks — if the URL is ever pasted into a browser tab.
  • Client-side error trackers — Sentry and similar capture request URLs in breadcrumbs. They redact Authorization headers automatically, but not query parameters.

I hit this writing a mobile client (Flutter, talking to v1 API) — Sentry breadcrumbs were exfiltrating the API key on every error report. Same risk applies to any third-party integration that logs requests.

Proposal

Also accept the key in a request header, with no breaking change to the existing query-string/body form:

  • Authorization: Bearer <key> (RFC 6750)
  • X-API-Key: <key>

Preference order on the server: header -> ?api_key= -> POST body. Existing clients keep working unchanged.

Patch

The change is small — one PHP file plus an .htaccess rule for the mod_php Authorization passthrough (FPM/CGI setups already pass it through). Branch on my fork:

I'd happily open this as a PR, but the repo restricts PR creation to collaborators. Options:

  1. A maintainer pulls the branch and reviews / merges directly
  2. Grant me temporary contributor access so I can open the PR through the normal review flow
  3. I can paste the diff into this issue if that's easier — let me know what you prefer

Test plan I ran

  • curl -H 'Authorization: Bearer <key>' .../api/v1/clients/read.php → 200, identical response shape to legacy
  • curl -H 'X-API-Key: <key>' .../api/v1/clients/read.php → 200
  • curl '.../api/v1/clients/read.php?api_key=<key>' → 200 (legacy unchanged)
  • curl .../api/v1/clients/read.php (no key) → 401 as before
  • Invalid key in either form → 401 with existing JSON error body

Not in scope

This is the smallest change that stops credentials leaking through transport. Per-device tokens, scopes, token revocation, and /auth/whoami are separate concerns and belong in their own follow-ups — happy to discuss those in a separate issue if there's interest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions