Expand description
AWS-style canonical request signed with SHA-256, replay-bounded
by an X-Date tolerance window. See hmac_auth::HmacAuthLayer.
HMAC-signed request authentication for service-to-service traffic.
AWS-style: each request carries X-Date and an Authorization
header containing a key id + HMAC-SHA256 over the canonical
request. The shared key is never transmitted, and replay attacks
are bounded by a configurable X-Date tolerance window.
Distinct from crate::api_keys (bearer-token style — the key
itself rides the wire on every call): pick HMAC when callers can
sign and you don’t trust the channel; pick bearer when TLS is
enough and you want the simplest possible client.
§Wire format
POST /webhooks/incoming HTTP/1.1
X-Date: 2026-05-02T12:34:56Z
Authorization: HMAC-SHA256 keyId=k_abc,signature=<base64>§Canonical request (what gets signed)
<UPPER-METHOD>\n
<PATH>\n
<SORTED-QUERY-STRING>\n
<X-DATE>\n
<HEX-SHA256(BODY)>Sorted query so ?b=2&a=1 and ?a=1&b=2 produce the same
signature. Body is hashed (SHA-256 hex) — saves the verifier from
buffering and re-hashing inside HMAC.
§Quick start
use rustango::hmac_auth::{HmacAuthLayer, KeyResolver};
use tower::ServiceBuilder;
use std::sync::Arc;
// Lookup function: key id -> Some(secret) or None for unknown.
let resolver: KeyResolver = Arc::new(|key_id: &str| {
if key_id == "k_abc" { Some(b"shared-secret-bytes".to_vec()) } else { None }
});
let inner = axum::Router::new().route("/webhooks/incoming", post(handle));
let app = ServiceBuilder::new()
.layer(HmacAuthLayer::new(resolver))
.service(inner);§Signing on the client side
Use [sign_request] to build the Authorization header value
that this layer will accept.
Structs§
Functions§
- sign_
now - Convenience: pick
now()as the date and return both headers (the date + the authorization). Date is RFC 3339 / ISO 8601. - sign_
request - Build the
Authorizationheader value for a request signed withsecretfor key idkey_id. Caller is responsible for settingX-Dateto a matching RFC 3339 timestamp.
Type Aliases§
- KeyResolver
- Closure that maps
key_id→Option<secret>. Implementors typically look up the key in a DB / cache. ReturningNonerejects the request with 401.