Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Psr7 Hmac Laravel Package

uma/psr7-hmac

PSR-7 HMAC authentication for PHP. Sign and verify HTTP requests with a shared secret, adding tamper-proof request signing to APIs and service-to-service calls. Framework-agnostic, PSR-15 middleware-friendly, and lightweight to drop into any PSR-7 stack.

View on GitHub
Deep Wiki
Context7

Getting Started

uma/psr7-hmac is a small shared-secret HMAC signing/verification library for PSR-7 requests. It is aimed at service-to-service authentication — internal APIs, webhooks, partner integrations — not user login or OAuth. The package is archived and has few dependents, so treat it as a compact reference implementation you are willing to own rather than a actively maintained dependency.

Minimal setup:

  1. Install it alongside a PSR-7 implementation and, if you want middleware verification, a PSR-15 dispatcher:

    composer require uma/psr7-hmac guzzlehttp/psr7
    
  2. Read src/ in this order: the specification (what bytes get signed), the calculator/signer (how the signature is produced), the key provider (mapping key IDs to secrets), and finally the middleware (verification and error handling). The tests are the best documentation of the exact wire format.

  3. Implement a key provider. At minimum it maps a key ID to a secret; in production it should read from your secret manager or database and support rotation.

  4. On the client, compute a signature and attach it plus a key ID and timestamp to the request. On the server, recompute and compare.

A first end-to-end use case — service A calls service B with a shared secret:

$keys = new InMemoryKeyProvider(['service-a' => $_ENV['HMAC_SECRET']]);
$calculator = new HmacCalculator(new DefaultSpecification());

$signature = $calculator->calculate($request, $keys->get('service-a'));

$request = $request
    ->withHeader('X-Hmac-Key', 'service-a')
    ->withHeader('X-Hmac-Timestamp', (string) time())
    ->withHeader('Authorization', "HMAC service-a:{$signature}");

Class names may differ slightly by release; verify against src/ before wiring it in. The important part is that both sides use the same specification and the same canonical request bytes.

Implementation Patterns

Sign once, at the transport boundary. The cleanest client pattern is a Guzzle middleware or handler-stack step that signs every outbound request to a given host. Business code then just calls $client->get(...) and never touches HMAC directly.

Verify early in the server pipeline. Insert verification before routing and before anything that mutates the request body. On success, attach the authenticated key ID as a request attribute ($request->attributes->set('hmac_key_id', $keyId)) so downstream code can authorize by client. On failure, return 401 without leaking whether the key ID or the signature was wrong.

Laravel wiring. Bind the key provider in a service provider:

$this->app->singleton(KeyProviderInterface::class, fn () => new DatabaseKeyProvider());

Register a verification middleware in app/Http/Middleware and add it to the api group in app/Http/Kernel.php (or bootstrap/app.php on Laravel 11+). For outgoing calls, register a Guzzle handler stack in a small HmacClientFactory service so signed clients are constructed consistently. Keep keys in config/hmac.php sourced from env/secret manager, never hard-coded.

Design a canonical request. The signature should cover method, path, sorted query parameters, a timestamp, a nonce, a body hash, and an explicit whitelist of headers (host, content-type, date). Do not sign mutable or proxy-added headers. Version the specification (e.g. X-Hmac-Version: 1) so you can change it without breaking existing clients.

Rotate keys by key ID. The header carries a key ID; the provider returns the current secret plus any previous secret still inside the rotation window. This lets you rotate without a flag day.

Use the calculator standalone when middleware does not fit. The signing core is useful inside a Laravel controller, a queued job, or a webhook sender. You do not need the full middleware stack to get value from the package.

Test both sides together. Freeze the clock, build a request, sign it, verify it, and assert failure cases (tampered body, wrong key, stale timestamp). Keep a known-vector test so a refactor cannot silently change the canonical string. If a partner implements the same spec, add contract tests against their implementation.

Gotchas and Tips

  • Archived and last released in 2020. Check its Composer constraints against your PHP and PSR-7 versions, especially on PHP 8.2+. Because the surface is small, vendoring or forking src/ is often safer than depending on an unmaintained package for security-sensitive code.

  • Laravel's Illuminate\Http\Request is not PSR-7. For incoming verification you either need a PSR-7 bridge (symfony/psr-http-message-bridge plus a PSR-7 factory) or you adapt the canonical-string/signature logic to Laravel's request. The Guzzle side is PSR-7 natively, so client signing is straightforward.

  • Body streams are consumable. Hashing the body can leave the stream pointer at the end. Call rewind() before hashing and ensure the request forwarded downstream still has a readable body. Middleware ordering matters: any body-mutating middleware before verification changes the bytes and breaks the signature.

  • Canonicalization mismatches are the #1 cause of intermittent 401s. Query parameter ordering, array encoding, trailing ?, header casing, multiple header values, and proxy-rewritten paths all differ between frameworks. Compare the exact string-to-sign on both sides when debugging; log it in non-production environments.

  • Use constant-time comparison. Ensure verification uses hash_equals, not ===. Check whether the package emits hex or base64 and make both sides agree.

  • Replay protection is your job. A timestamp header alone is not enough unless you reject old requests; add a nonce cache (Redis with TTL) or at least a narrow clock-skew window (±300s) and require UTC.

  • Proxies and load balancers change host, scheme, and path. Sign what the application sees, and configure trusted proxies in Laravel so the verified host/path matches the client's intent.

  • PSR-7 immutability. Middleware must pass the new request ($handler->handle($request->withAttribute(...))), not mutate and ignore the return value.

  • Extension points: custom Specification for extra headers or a different canonical format, custom KeyProvider for database/secret-manager lookup with caching, and custom middleware for structured error responses and metrics. The calculator itself is the stable core — everything else is replaceable.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table