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

Sapient Laravel Package

paragonie/sapient

Sapient is a PHP cryptography library from Paragon Initiative Enterprises offering simple, misuse-resistant APIs for authenticated symmetric encryption, public-key encryption, and digital signatures, built on libsodium with versioned ciphertexts and key-management helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require paragonie/sapient

You need PHP 8.x, a PSR-7 implementation (guzzlehttp/psr7 is already pulled in transitively by Laravel's Guzzle dependency), and ideally ext-sodium (Sapient falls back to paragonie/sodium_compat, but native sodium is faster).

Where to look first, in this order:

  1. SAPIClient and SAPIServer — the only two classes most apps ever touch.
  2. CryptographyKeys\* — SharedAuthenticationKey (HMAC/signing) and SharedEncryptionKey (AEAD body encryption), plus the asymmetric SigningPublicKey/SealingPublicKey variants.
  3. SimpleSecurityMonitor and Contract\SecurityMonitorInterface — replay/abuse tracking.
  4. Exception\SapientException and Exception\CryptoException — the only things you should catch.

The smallest useful use case is signing outbound requests with a shared key:

use ParagonIE\Sapient\SAPIClient;
use ParagonIE\Sapient\CryptographyKeys\SharedAuthenticationKey;
use GuzzleHttp\Psr7\Request;

$authKey = new SharedAuthenticationKey($secret32Bytes); // base64/raw 32-byte secret
$client  = (new SAPIClient())->setAuthenticationKey($authKey);

$psr7 = $client->signRequest(
    new Request('POST', 'https://api.example.com/orders',
        ['Content-Type' => 'application/json'], $jsonBody)
);

On the receiving end:

use ParagonIE\Sapient\SAPIServer;
use ParagonIE\Sapient\SimpleSecurityMonitor;

$server = (new SAPIServer(new SimpleSecurityMonitor()))
    ->setAuthenticationKey($authKey);

$request = $server->verifySignedRequest($psr7); // throws SapientException on failure

The public surface is small; if a method name differs in your installed version (the package has had a couple of API revisions), grep for public function inside SAPIClient.php / SAPIServer.php — the flow above is always the same shape: signRequest/encryptRequest on the client, verifySignedRequest/decryptRequest on the server, and the mirrored verifyResponse/decryptResponse on the way back.

Implementation Patterns

Sign-only vs. encrypt-and-sign. Signing gives you integrity and authentication (the server knows the request came from a holder of the key and wasn't tampered with on the wire or in a proxy). Encryption additionally hides the body. Day-to-day, signing alone is enough for most APIs, especially when TLS is already protecting confidentiality — encrypt only when payloads contain sensitive material (PII, tokens, webhook secrets) that must survive a TLS-terminating proxy you don't control. Do not treat Sapient as a TLS replacement; it's a defense-in-depth layer.

Laravel service binding. Put the keys in config/services.php sourced from .env, and register the client/server as singletons in a service provider:

$this->app->singleton(SAPIClient::class, fn () => (new SAPIClient())
    ->setAuthenticationKey(new SharedAuthenticationKey(base64_decode(config('services.partner.auth_key'))))
    ->setEncryptionKey(new SharedEncryptionKey(base64_decode(config('services.partner.enc_key')))));

Never regenerate keys on boot; they must be identical across processes and deployments.

Inbound middleware. Verify before anything consumes the body, otherwise you'll be verifying an empty stream:

public function handle($request, Closure $next)
{
    $psr7 = (new PsrHttpFactory(...))->createRequest($request); // symfony/psr-http-message-bridge
    try {
        $verified = app(SAPIServer::class)->verifySignedRequest($psr7);
    } catch (SapientException $e) {
        return response()->json(['error' => 'invalid signature'], 401);
    }
    return $next($request);
}

Register it in a dedicated sapient middleware group on the API routes; keep it ahead of throttle and anything that reads input.

Outbound Guzzle middleware. Rather than signing at every call site, push a handler-stack middleware onto the Guzzle client and sign inside it. That keeps the signing logic in one place and makes it work with Laravel's Http:: facade if you supply the handler via Http::withOptions(['handler' => $stack]).

Replay protection. Sapient's SimpleSecurityMonitor gives you basic checks, but for anything multi-process or multi-host you want a SecurityMonitorInterface implementation backed by Redis or the cache, storing the request nonce (or signature) with a TTL equal to your accepted clock skew. Register it in the SAPIServer constructor and make the TTL short (60–300s).

Key rotation. Add a X-Key-Id header outside of Sapient and dispatch to the right key on the server. Sapient has no key-id concept of its own, and you will need rotation long before you think you will.

Testing. Feature tests can construct signed requests with the exact same SAPIClient and a test key from phpunit.xml, so you're testing the real verification path instead of stubbing the middleware.

Gotchas and Tips

  • PSR-7 is immutable. $request = $client->signRequest($request) — forgetting the assignment silently sends an unsigned request. This is the single most common mistake.
  • Rewind body streams. Guzzle streams are read-once. Call $request->getBody()->rewind() before signing and again before decrypting, or you'll sign/verify an empty string.
  • Content-Length and proxies. Encryption changes the body length. If any intermediary recomputes Content-Length or mangles Host, and your signature covers those headers, verification fails. Include only stable headers in the canonical form, and confirm which headers the library includes before adding your own.
  • Order of operations on responses. Decrypt the response before handing it to Laravel's JSON parsing / $response->json(). The body is ciphertext until decrypted.
  • Don't leak keys into logs. Sapient exceptions are deliberately vague, which is good — don't "help" by logging $request->getBody() on failure; you'll log either plaintext or ciphertext that helps an attacker.
  • SimpleSecurityMonitor is not a rate limiter. It's a minimal replay guard. Use Laravel's throttle middleware for actual rate limiting and combine both.
  • Clock skew. If your monitor or any timestamp header rejects requests, allow 60–120s of skew; container clocks drift.
  • Asymmetric mode requires key distribution. If you can't share a symmetric key with a third party, use the signing/sealing keypairs — but you now own public-key provisioning, which Sapient does not solve for you.
  • Extension points: SecurityMonitorInterface (custom nonce store), the CryptoEngine abstraction (swap in a different backend), and the low-level Cryptography helper if you need to sign a payload outside an HTTP message (e.g., a queued job envelope).
  • Maturity signal: 309 stars and one maintainer (ParagonIE, highly reputable in PHP crypto, but a small team). Pin the version in composer.json, read the changelog on upgrades, and keep a thin wrapper class around SAPIClient/SAPIServer so you can swap or patch if the package stalls.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums