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

Laravel Auth Signature Laravel Package

aporat/laravel-auth-signature

Laravel middleware that authenticates incoming HTTP requests using signed signatures, letting you verify request integrity and identity without exposing credentials on each call.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require aporat/laravel-auth-signature — package discovery registers the service provider, the ValidateAuthSignature middleware alias and the Http::withSignature() macro, so there is nothing to wire up in config/app.php.
  • Publish the config (php artisan vendor:publish and pick the AuthSignature provider) and define at least one client: client_id, client_secret, and the auth version string you intend to use. Both sides of the integration must agree on the version.
  • First use case — protect an internal API: attach ValidateAuthSignature to an api route group (or a single route) and confirm an unsigned curl now fails. Then, from the calling Laravel app, replace Http::post(...) with:
Http::withSignature('my-client-id')->post('https://api.example.test/orders', $payload);

If it round-trips, the two halves are configured consistently. That single call is the whole happy path — the macro signs the final method, path, query string and JSON/form body at send time, using the same SignatureGenerator the middleware validates with, so there is no canonicalisation to keep in sync by hand.

  • Where to look first: the README's signature-failure mapping snippet (see Gotchas — the default behaviour is an HTTP 500), then Aporat\AuthSignature\Client\RequestSigner (the single signing implementation shared by the macro and the command), SignatureGenerator::stringToSign() (canonical string) and SignatureGenerator::generate().
  • First debugging tool: php artisan auth-signature:sign --debug. It prints the X-Auth-* headers plus the canonical string being signed, without ever printing the secret — so it is safe to run on a shared terminal and paste into a ticket.

Implementation Patterns

Outgoing calls (the default path). Use the macro everywhere you call a signature-protected endpoint. It signs at send time, which means query parameters, ->asForm() vs JSON, and payloads assembled by chained ->withQueryParameters()/->withBody() are all covered without ordering rules. Keep the client ID in config, not inline strings:

Http::withSignature(config('services.partner.client_id'))
    ->acceptJson()
    ->retry(3, 200)
    ->post($url, $payload);

Per-request secret override. The third argument of withSignature($clientId, $authVersion, $secret) replaces the stored client_secret for that call only. Two good uses: multi-tenant integrations where each tenant supplies its own secret, and secret rotation — start signing with the new secret while the receiver still accepts the old one, then flip over.

Non-Laravel / PSR-7 stacks. When you are not going through the HTTP client (a custom Guzzle handler, a third-party SDK that takes a RequestInterface, an outbound webhook relay), use Aporat\AuthSignature\Client\RequestSigner on the PSR-7 request instead of hand-rolling headers. Resist reimplementing the canonical string; both the macro and the command delegate to it, and anything you write yourself will drift from ValidateAuthSignature at the first edge case.

Integration tips

  • Apply the middleware to route groups, not individual routes, and put it before anything that mutates the request body or rewrites the path. Body- or query-normalising middleware downstream of validation will invalidate the signature.
  • Keep the verifying middleware early in the group so unauthenticated junk never reaches your controllers or hits the database.
  • Treat the artisan command as the contract documentation: --curl produces a ready-to-run reproduction command, --debug/-v shows the canonical string. When a partner reports a failure, ask for their --debug output and diff it against your own invocation — the mismatch is almost always path, query encoding or body encoding, and it is visible in one glance.
  • In tests, Http::fake() plus Http::assertSent(fn ($request) => $request->hasHeader('X-Auth-Signature')) (adjust to the header the command prints) is enough to prove the macro is attached. For canonical-string assertions, call SignatureGenerator::stringToSign() directly rather than asserting a hard-coded hash — the hash will churn on any canonicalisation change and the string is the meaningful thing to pin.
  • Sign at the edge, not in advance. Do not pre-sign a request and store it for later dispatch; build the call inside the job/command that actually sends it.

Gotchas and Tips

  • Signature failures render as HTTP 500, not 401/403. This is the biggest day-one surprise: a wrong secret looks like a server crash to clients and pollutes your error tracking. The README ships a copy-paste bootstrap/app.php snippet (in withExceptions()) to map the middleware's exception to its proper 4xx code — apply it before you go live, and use the class name from the README rather than guessing at it. Doing this also makes the failure legible in logs and lets clients implement sane retry logic (they should not retry a 401).
  • Replay protection is only the ±300 s timestamp window. There is no nonce cache, so a captured request can be replayed for up to five minutes. If the signed endpoints are idempotent and read-only this is fine; if they move money or create records, add your own idempotency key and a nonce/used-signature store in front of the handler. Do not assume the package is doing this for you.
  • Clock skew is the silent killer. A container or VM drifting more than five minutes fails every request with an opaque error. Check date -u on both hosts before debugging anything else, and keep NTP enabled. Long retry backoffs have the same failure mode — a request that waits six minutes between attempts is signed with a stale timestamp, so cap backoff below the window.
  • Retries should re-sign. Because signing happens at send time, a normal ->retry() re-signs each attempt and gets a fresh timestamp. Anything you build that reuses a prepared request object must respect the same rule.
  • Body encoding is part of the signature. ->asForm() and ->json() are different canonical inputs, and re-encoding JSON yourself with different flags (JSON_UNESCAPED_SLASHES, key ordering) produces a different string. Let the macro see the real payload; if you must send a raw body, send byte-identical content on both sides. --debug is how you confirm which form was actually signed.
  • Use --debug/-v freely — secrets are never printed. The canonical string is safe to paste into a ticket. Still, treat it as internal: it reveals endpoint shapes and payload structure.
  • Composer now requires illuminate/http, illuminate/routing, illuminate/console and guzzlehttp/guzzle directly. That is good for consumers (no more accidental reliance on a transitive Guzzle), but it means your Laravel major must satisfy those constraints — check before bumping the package on an older app. PHPUnit ^11 || ^12 only matters if you fork the package or run its suite; it does not affect your app's test setup.
  • The published package no longer ships tests, CI config or dev tooling (.gitattributes). Don't expect vendor/aporat/.../tests to exist, and don't write tooling that assumes it. If you need to verify behaviour, the new end-to-end route tests live in the repository, not in the tarball — clone the repo to read them.
  • Extension points worth knowing: SignatureGenerator::generate($..., $secret) for rotation-style signing outside the HTTP client, stringToSign() for logging/diffing, and RequestSigner as the seam if you ever need to sign a non-Laravel request. Everything else — new headers, alternate canonicalisation — should be treated as a fork rather than a local patch, or the two halves of the integration will silently diverge.
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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox