A PHP 8.4+ implementation of HTTP Message Signatures as specified in RFC 9421.
- âś… Full RFC 9421 compliance
- âś… PSR-7 compliant - Works with any PSR-7 HTTP message implementation
- âś… Support for multiple signature algorithms:
- HMAC-SHA256
- RSA-SHA256
- Ed25519
- âś… Signature creation and verification
- âś… Structured fields parsing for
signature-inputandsignatureheaders - âś… Component derivation (headers, query parameters, request target, etc.)
- âś… Immutable message handling (respects PSR-7 immutability)
composer require timkelty/http-message-signatures- PHP 8.4 or higher
- PSR-7 HTTP message implementation (e.g.,
guzzlehttp/psr7,nyholm/psr7,slim/psr7)
This package uses well-maintained, industry-standard libraries:
- bakame/http-structured-fields - For parsing and formatting HTTP Structured Fields (RFC 8941) used in signature headers
use HttpMessageSignatures\Signer;
use HttpMessageSignatures\Algorithm\HmacSha256;
use GuzzleHttp\Psr7\Request;
// Create a PSR-7 request
$request = new Request(
'POST',
'https://api.example.com/resource',
[
'Host' => 'api.example.com',
'Content-Type' => 'application/json',
'Date' => gmdate('D, d M Y H:i:s \G\M\T'),
],
'{"data":"value"}'
);
// Create signer with HMAC-SHA256 algorithm
$signer = new Signer(new HmacSha256('your-secret-key'));
// Sign the request (returns a new immutable PSR-7 message)
$signedRequest = $signer->sign(
$request,
['@method', '@path', '@authority', 'content-type', 'date'],
[
'keyid' => 'my-key-id',
'signatureId' => 'sig1',
'created' => time(),
'expires' => time() + 300, // Optional: 5 minutes
]
);
// The original request is unchanged (PSR-7 immutability)
// $signedRequest is a new instance with Signature and Signature-Input headersuse HttpMessageSignatures\Verifier;
use HttpMessageSignatures\Algorithm\HmacSha256;
use HttpMessageSignatures\Exception\VerificationException;
$verifier = new Verifier(new HmacSha256('your-secret-key'));
try {
// Verify the signature (returns true if valid)
$isValid = $verifier->verify($signedRequest);
if ($isValid) {
echo "Signature is valid!\n";
}
} catch (VerificationException $e) {
echo "Verification failed: " . $e->getMessage() . "\n";
}use HttpMessageSignatures\Signer;
use HttpMessageSignatures\Algorithm\RsaSha256;
// Load your private key (for signing)
$privateKey = file_get_contents('/path/to/private-key.pem');
// Optionally provide public key (for verification)
$publicKey = file_get_contents('/path/to/public-key.pem');
$signer = new Signer(new RsaSha256($privateKey, $publicKey));
$signedRequest = $signer->sign(
$request,
['@method', '@path', '@authority', 'content-type'],
['keyid' => 'rsa-key-1']
);use HttpMessageSignatures\Signer;
use HttpMessageSignatures\Algorithm\Ed25519;
// Ed25519 requires the sodium extension
$privateKey = sodium_crypto_sign_seed_keypair(...);
$publicKey = sodium_crypto_sign_publickey($privateKey);
$signer = new Signer(new Ed25519($privateKey, $publicKey));
$signedRequest = $signer->sign(
$request,
['@method', '@path', '@authority'],
['keyid' => 'ed25519-key-1']
);The following components can be included in signatures:
Derived Components:
@method- HTTP method@path- Request path@query- Query string@authority- Host and port@scheme- URI scheme@target-uri- Full URI@request-target- Request target@status- Response status code (for responses)
Headers:
- Any header name (e.g.,
content-type,date,authorization)
Query Parameters:
@query-param;name="paramname"- Specific query parameter
This package includes first-class Laravel support:
composer require timkelty/http-message-signaturesPublish the configuration file:
php artisan vendor:publish --tag=http-message-signatures-configConfigure your keys in .env:
HTTP_SIGNATURE_ALGORITHM=hmac-sha256
HTTP_SIGNATURE_HMAC_SECRET_KEY=your-secret-key
HTTP_SIGNATURE_KEY_ID=my-key-iduse HttpMessageSignatures\Laravel\Facades\HttpMessageSigner;
use HttpMessageSignatures\Laravel\Facades\HttpMessageVerifier;
// Sign a request
$signedRequest = HttpMessageSigner::sign($request, [
'@method', '@path', '@authority', 'content-type'
], ['keyid' => 'my-key']);
// Verify a request
$isValid = HttpMessageVerifier::verify($request);use function HttpMessageSignatures\Laravel\sign_request;
use function HttpMessageSignatures\Laravel\sign_http_message;
use function HttpMessageSignatures\Laravel\verify_http_message;
// Sign a Laravel Request
$signedRequest = sign_request($request);
// Sign any HTTP message
$signedMessage = sign_http_message($message);
// Verify an HTTP message
$isValid = verify_http_message($message);use HttpMessageSignatures\Signer;
use HttpMessageSignatures\Verifier;
class MyController
{
public function __construct(
private Signer $signer,
private Verifier $verifier
) {}
public function sign(Request $request)
{
$signed = $this->signer->sign($request, [
'@method', '@path', '@authority'
], ['keyid' => 'my-key']);
return response()->json(['signed' => true]);
}
}The package automatically registers a service provider. All classes are bound in the container and can be injected via dependency injection.
This package is fully PSR-7 compliant:
- Works with any PSR-7 implementation (
guzzlehttp/psr7,nyholm/psr7,slim/psr7, etc.) - Respects PSR-7 immutability - all methods return new message instances
- Uses only PSR-7 interfaces (
MessageInterface,RequestInterface,ResponseInterface) - No direct dependencies on specific PSR-7 implementations
composer test
# or
./vendor/bin/pestThis project uses Laravel Pint for code style formatting:
composer pint
# or
./vendor/bin/pintThis project uses PHPStan for static analysis:
composer phpstan
# or
./vendor/bin/phpstan analyseMIT