hashnz/hawk-server-bundle
Symfony bundle that adds server-side Hawk HTTP authentication to your app. Validate Hawk Authorization headers, verify MACs, timestamps, and nonces, and protect routes with configurable credentials.
This is a Symfony bundle, not a Laravel package — there is no service provider, no config/hawk.php, and no facade. As a Laravel developer, you have two realistic ways in:
ts/nonce for replay protection). Reading this bundle's validator is often faster than reading the RFC-equivalent spec, and the logic ports to a Laravel middleware in ~100 lines.Minimal steps if you do install it:
composer require hashnz/hawk-server-bundle
// config/bundles.php
Hashnz\HawkServerBundle\HawkServerBundle::class => ['all' => true],
Then look in this order:
DependencyInjection/Configuration.php — this is the authoritative list of config keys; the README may lag behind it.config/packages/hawk_server.yaml (typical shape: a map of id => shared_key, plus a timestamp-skew window).First use case: protect a machine-to-machine endpoint — internal service calls, webhooks, a CLI agent — where both sides already share a secret. Hawk is a poor fit for browser clients or third-party integrations, because every client needs a provisioned key and a roughly accurate clock.
Keep Hawk on its own firewall. Don't mix it into your session-based main firewall; register a dedicated one for /api so a failed Hawk header returns a clean 401 instead of redirecting to a login form.
Credential lookup is the main extension point. Static id => key config is fine for one or two clients. Beyond that, implement the bundle's credential-provider interface against your api_clients table (store a hash of the key, not the key). Cache lookups — this runs on every request.
You must supply a nonce store. Hawk's replay protection is only as good as the cache behind it. Redis with SETNX + TTL equal to your skew window (e.g. 60s) is the standard shape; key it as hawk:nonce:{id}:{nonce} so one noisy client can't evict another's entries.
Client side is not in this bundle. It's server-only. Use the hawk npm package, a Guzzle Hawk middleware, or hand-roll it. In Laravel, generating the header looks like:
$ts = time(); $nonce = bin2hex(random_bytes(6));
$normalized = implode("\n", [
'hawk.1.header', $ts, $nonce, $method, $path.$query, $host, $port, $hash, $ext,
]);
$mac = base64_encode(hash_hmac('sha256', $normalized, $key, true));
Note the hash component is the SHA-256 of the body (empty string if no body) — that's what binds the payload to the header.
Testing: inject a fixed clock and a deterministic nonce generator, then assert on the exact normalized string. Header-validation bugs are almost always canonicalization bugs, and a test that prints the string you signed is worth more than ten integration tests.
ts deltas on rejection.host and port must match what the client signed. Behind a load balancer, configure trusted proxies or the MAC will never verify on non-default ports. This is the single most common "it works locally" failure.ext in the MAC if present. Omitting it silently allows tampering with that field.WWW-Authenticate: Hawk. Valid MAC but unauthorized client → 403. Symfony's security listeners sometimes flatten these.port or a double \n) explains nearly every verification failure.hash_equals() and a Redis nonce check is lower risk than bridging a Symfony security bundle — you get the same protocol guarantees and control the error responses, the clock source, and the credential store directly.How can I help you explore Laravel packages today?