How it works¶
The signed message¶
URLAuth.sign builds a canonical byte string from the
URL and the expiry, then signs it with the configured backend:
- The query is parsed and the
sig/expparams — plus anything passed toignore_query_params— are dropped, then re-encoded. This canonicalisation means signing and verification agree regardless of incoming encoding or the ordering of unrelated parameters. - The expiry, scheme, netloc, path, params, and canonical query are joined with newlines. A newline cannot appear in a URL component, so field boundaries can never be confused (e.g. a value ending in what looks like the next field).
- The resulting
utf-8bytes are handed to the backend'ssign.
URLAuth.verify rebuilds the same message and checks
the signature against every configured key, so a signature made with a
rotated-out key still verifies until it expires. HMAC comparisons are
constant-time to avoid timing attacks.
Keys and ids¶
Every key carries a stable id. If you don't supply one, it's a SHA-512
fingerprint:
- An
HMACKeyfingerprints the secret itself (safe because SHA-512 is one-way, andreprtruncates). - An
Ed25519KeyPairfingerprints its public bytes, so it shares the id of the matchingEd25519PublicKeyand never leaks the seed.
Backends¶
A Backend owns the algorithm-specific work: parsing key
values into Key instances, signing, and verifying. It isn't tied to one
algorithm — each key already knows whether it is HMAC or Ed25519, so the backend
dispatches on the key's type. Everything algorithm-agnostic — URL
canonicalisation, expiry, key rotation — lives in URLAuth. A
KeySet pairs a collection of keys with the backend that
parses them, and a single keyset can mix HMAC and Ed25519 keys freely.