### Install Development Dependencies Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Use Make to install project dependencies. This command should be run in the project's root directory. ```sh # Install depdendencies make install ``` -------------------------------- ### Install HTTP HMAC Signer using Composer Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Add the library as a dependency to your project's composer.json file using Composer. ```json { "require": { "acquia/http-hmac-php": "^5.0" } } ``` -------------------------------- ### Setup HMAC Test Case in Symfony Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Create a base test case for HMAC authentication. This sets up the test client and configures the HMAC key for requests. ```php namespace MyApp\Bundle\AppBundle\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpFoundation\Response; use Symfony\Bundle\FrameworkBundle\Client; use Acquia\Hmac\Key; class HmacTestCase extends WebTestCase { /** * @var Client */ private $client; protected static function createClient(array $options = array(), array $server = array()) { $kernel = static::bootKernel($options); $client = $kernel->getContainer()->get('test.client.hmac'); $client->setServerParameters($server); return $client; } protected function setUp() { $this->client = static::createClient(); $this->client->setKey(new Key('my-key', 'my-not-really-secret')); } ``` -------------------------------- ### Run Test Suite Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Execute the project's test suite using Make. This command verifies the functionality of the implemented code. ```sh # Run test suite make test ``` -------------------------------- ### Load Keys by ID for Server-Side Authentication (PHP) Source: https://context7.com/acquia/http-hmac-php/llms.txt Holds a map of id => secret pairs and returns a Key object when given an ID. Base64KeyLoader encodes plain-text secrets automatically. ```php use Acquia\Hmac\KeyLoader; use Acquia\Hmac\Base64KeyLoader; // Keys stored as id => Base64-encoded-secret $keyLoader = new KeyLoader([ 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059' => base64_encode('my-raw-shared-secret'), 'abc123' => base64_encode('another-secret'), ]); $key = $keyLoader->load('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059'); // Returns a Key object, or false if not found. // Base64KeyLoader encodes plain-text secrets automatically: $plainLoader = new Base64KeyLoader([ 'my-key-id' => 'plain-text-secret', // no manual base64_encode needed ]); $key2 = $plainLoader->load('my-key-id'); echo $key2->getSecret(); // base64_encode('plain-text-secret') ``` -------------------------------- ### Configure Symfony integration for HMAC authentication Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Include necessary Symfony and Zend Diactoros libraries for integration. The provided YAML configuration demonstrates how to set up services for key loading, request authentication, and response signing within a Symfony application. ```json { "require": { "symfony/psr-http-message-bridge": "~0.1", "symfony/security": "~3.0", "zendframework/zend-diactoros": "~1.3.5" } } ``` ```yaml # app/config/parameters.yml parameters: hmac_keys: {"key": "secret"} # app/config/services.yml services: hmac.keyloader: class: Acquia\Hmac\KeyLoader arguments: $keys: '%hmac_keys%' hmac.request.authenticator: class: Acquia\Hmac\RequestAuthenticator arguments: - '@hmac.keyloader' public: false hmac.response.signer: class: Acquia\Hmac\Symfony\HmacResponseListener tags: - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse } hmac.entry-point: class: Acquia\Hmac\Symfony\HmacAuthenticationEntryPoint hmac.security.authentication.provider: class: Acquia\Hmac\Symfony\HmacAuthenticationProvider arguments: - '@hmac.request.authenticator' public: false hmac.security.authentication.listener: class: Acquia\Hmac\Symfony\HmacAuthenticationListener arguments: ['@security.token_storage', '@security.authentication.manager', '@hmac.entry-point'] public: false ``` -------------------------------- ### Create an Authentication Key (PHP) Source: https://context7.com/acquia/http-hmac-php/llms.txt Encapsulates the key ID and its Base64-encoded shared secret. The secret MUST be Base64-encoded. If the API provider already encoded it, do not re-encode. ```php use Acquia\Hmac\Key; // The secret MUST be Base64-encoded. If the API provider already encoded it, do not re-encode. $keyId = 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059'; $keySecret = base64_encode('my-raw-shared-secret'); // encode only if not already encoded $key = new Key($keyId, $keySecret); echo $key->getId(); // e7fe97fa-a0c8-4a42-ab8e-2c26d52df059 echo $key->getSecret(); // bXktcmF3LXNoYXJlZC1zZWNyZXQ= ``` -------------------------------- ### Verify Incoming Responses (Client Side) Source: https://context7.com/acquia/http-hmac-php/llms.txt Verifies the X-Server-Authorization-HMAC-SHA256 header on a received response to confirm the server used the correct key for signing. Requires the signed request and the key used for authentication. ```php use Acquia\Hmac\Key; use Acquia\Hmac\ResponseAuthenticator; use Acquia\Hmac\Exception\MalformedResponseException; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); // $signedRequest — the PSR-7 request that was sent (after RequestSigner added its headers) // $response — the PSR-7 response received from the server $authenticator = new ResponseAuthenticator($signedRequest, $key); try { if ($authenticator->isAuthentic($response)) { echo 'Response verified — server identity confirmed.'; } else { echo 'Response signature mismatch.'; } } catch (MalformedResponseException $e) { // Response is missing X-Server-Authorization-HMAC-SHA256 echo 'Malformed response: ' . $e->getMessage(); } ``` -------------------------------- ### KeyLoader - Loading Keys by ID (Server Side) Source: https://context7.com/acquia/http-hmac-php/llms.txt `KeyLoader` holds a map of `id => secret` pairs and returns a `Key` object when given an ID. `Base64KeyLoader` is a subclass that Base64-encodes plain-text secrets automatically. ```APIDOC ## KeyLoader — Loading Keys by ID (Server Side) ### Description `KeyLoader` holds a map of `id => secret` pairs and returns a `Key` object when given an ID. Used by `RequestAuthenticator` on the server to look up the secret for an incoming request. `Base64KeyLoader` is a subclass that Base64-encodes plain-text secrets before returning them. ### Usage ```php use Acquia\Hmac\KeyLoader; use Acquia\Hmac\Base64KeyLoader; // Keys stored as id => Base64-encoded-secret $keyLoader = new KeyLoader([ 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059' => base64_encode('my-raw-shared-secret'), 'abc123' => base64_encode('another-secret'), ]); $key = $keyLoader->load('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059'); // Returns a Key object, or false if not found. // Base64KeyLoader encodes plain-text secrets automatically: $plainLoader = new Base64KeyLoader([ 'my-key-id' => 'plain-text-secret', // no manual base64_encode needed ]); $key2 = $plainLoader->load('my-key-id'); echo $key2->getSecret(); // base64_encode('plain-text-secret') ``` ``` -------------------------------- ### Key - Creating an Authentication Key Source: https://context7.com/acquia/http-hmac-php/llms.txt The `Key` class encapsulates the key ID and its Base64-encoded shared secret. It is the fundamental credential object passed to signers and authenticators. ```APIDOC ## Key - Creating an Authentication Key ### Description `Key` encapsulates the key ID and its Base64-encoded shared secret. It is the fundamental credential object passed to signers and authenticators throughout the library. ### Usage ```php use Acquia\Hmac\Key; // The secret MUST be Base64-encoded. If the API provider already encoded it, do not re-encode. $keyId = 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059'; $keySecret = base64_encode('my-raw-shared-secret'); // encode only if not already encoded $key = new Key($keyId, $keySecret); echo $key->getId(); // e7fe97fa-a0c8-4a42-ab8e-2c26d52df059 echo $key->getSecret(); // bXktcmF3LXNoYXJlZC1zZWNyZXQ= ``` ``` -------------------------------- ### Declare HMAC Test Client Service Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Declare a service for the HMAC client in your test parameters file. This is used for testing controllers that rely on HMAC authentication. ```yaml # app/config/parameters_test.yml services: test.client.hmac: class: Acquia\Hmac\Test\Mocks\Symfony\HmacClient arguments: ['@kernel', '%test.client.parameters%', '@test.client.history', '@test.client.cookiejar'] ``` -------------------------------- ### ResponseSigner Source: https://context7.com/acquia/http-hmac-php/llms.txt Appends an X-Server-Authorization-HMAC-SHA256 header to a PSR-7 response, allowing the client to verify the response originated from the legitimate server. It requires the authenticated key and the original signed request. ```APIDOC ## ResponseSigner — Signing Outgoing PSR-7 Responses (Server Side) `ResponseSigner` appends an `X-Server-Authorization-HMAC-SHA256` header to a PSR-7 response, letting the client verify the response came from the legitimate server. It requires the authenticated key and the original signed request. ```php use Acquia\Hmac\ResponseSigner; use Nyholm\Psr7\Response; use Nyholm\Psr7\Stream; // $key — the Key returned by RequestAuthenticator::authenticate() // $request — the original PSR-7 request (already bearing Authorization header) $signer = new ResponseSigner($key, $request); $body = Stream::create('{"widget":{"id":42,"color":"blue"}}'); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $signedResponse = $signer->signResponse($response); echo $signedResponse->getHeaderLine('X-Server-Authorization-HMAC-SHA256'); // base64-encoded HMAC-SHA256 of (nonce + "\n" + timestamp + "\n" + responseBody) ``` ``` -------------------------------- ### AuthorizationHeader::createFromRequest Source: https://context7.com/acquia/http-hmac-php/llms.txt Parses an existing `Authorization` header from an incoming PSR-7 request into a structured `AuthorizationHeader` object, exposing all individual fields. ```APIDOC ## AuthorizationHeader::createFromRequest ### Description Parses an existing `Authorization` header from an incoming PSR-7 request into a structured `AuthorizationHeader` object, exposing all individual fields. ### Usage ```php use Acquia\Hmac\AuthorizationHeader; use Acquia\Hmac\Exception\MalformedRequestException; // $request is a PSR-7 RequestInterface with an Authorization header already set try { $authHeader = AuthorizationHeader::createFromRequest($request); echo $authHeader->getRealm(); // e.g. "MyRealm" echo $authHeader->getId(); // key ID echo $authHeader->getNonce(); // UUID nonce echo $authHeader->getVersion(); // "2.0" echo $authHeader->getSignature(); // Base64 signature print_r($authHeader->getCustomHeaders()); // ['X-Custom-1', 'X-Custom-2'] } catch (MalformedRequestException $e) { // Authorization header missing or malformed (missing realm/id/nonce/version/signature) echo 'Parse error: ' . $e->getMessage(); } ``` ``` -------------------------------- ### Configure HMAC Firewall in Symfony Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Add this configuration to your security.yml file to enable HMAC authentication for specific API routes. Ensure the pattern matches your API endpoints. ```yaml security: # ... firewalls: hmac_auth: pattern: ^/api/ stateless: true hmac_auth: true ``` -------------------------------- ### Sign an API request using Guzzle and HmacAuthMiddleware Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Use the HmacAuthMiddleware with Guzzle to automatically sign outgoing API requests. Ensure the Key ID, secret, and realm are correctly configured. Custom headers can also be included in the signature. ```php require_once 'vendor/autoload.php'; use Acquia\Hmac\Guzzle\HmacAuthMiddleware; use Acquia\Hmac\Key; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; // Create the HTTP HMAC key. // A key consists of and ID and a Base64-encoded shared secret. // Note: the API provider may have already encoded the secret. In this case, it should not be re-encoded. $key_id = 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059'; $key_secret = base64_encode('secret'); $key = new Key($key_id, $key_secret); // Optionally, you can provide additional headers when generating the signature. // The header keys need to be provided to the middleware below. $headers = [ 'X-Custom-1' => 'value1', 'X-Custom-2' => 'value2', ]; // Specify the API's realm. // Consult the API documentation for this value. $realm = 'Acquia'; // Create a Guzzle middleware to handle authentication during all requests. // Provide your key, realm and the names of any additional custom headers. $middleware = new HmacAuthMiddleware($key, $realm, array_keys($headers)); // Register the middleware. $stack = HandlerStack::create(); $stack->push($middleware); // Create a client. $client = new Client([ 'handler' => $stack, ]); // Request. try { $result = $client->get('https://service.acquia.io/api/v1/widget', [ 'headers' => $headers, ]); } catch (ClientException $e) { print $e->getMessage(); $response = $e->getResponse(); } print $response->getBody(); ``` -------------------------------- ### AuthorizationHeaderBuilder for Manual Header Construction Source: https://context7.com/acquia/http-hmac-php/llms.txt Constructs the `Authorization` header value manually. Use this when you need fine-grained control over the header components or are integrating with a custom HTTP layer. It requires a PSR-7 request and a Key object. ```php use Acquia\Hmac\AuthorizationHeaderBuilder; use Acquia\Hmac\Key; use Nyholm\Psr7\Request; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); $request = (new Request('GET', 'https://api.example.com/v1/items')) ->withHeader('X-Authorization-Timestamp', (string) time()); $builder = new AuthorizationHeaderBuilder($request, $key); $builder->setRealm('MyRealm'); $builder->setId($key->getId()); $builder->setCustomHeaders(['X-Custom-Header']); $authHeader = $builder->getAuthorizationHeader(); echo $authHeader->getRealm(); // MyRealm echo $authHeader->getId(); // e7fe97fa-a0c8-4a42-ab8e-2c26d52df059 echo $authHeader->getNonce(); // auto-generated v4 UUID echo $authHeader->getVersion(); // 2.0 echo $authHeader->getSignature(); // base64-encoded HMAC-SHA256 // Cast to string for direct use in an Authorization header: echo (string) $authHeader; // acquia-http-hmac realm="MyRealm",id="e7fe97fa...",nonce="...",version="2.0",headers="",signature="..." ``` -------------------------------- ### HMAC-SHA256 Signing and SHA256 Hashing with Digest Source: https://context7.com/acquia/http-hmac-php/llms.txt Instantiate the Digest class to sign messages with HMAC-SHA256 and hash request bodies with SHA256. The default algorithm is sha256, but others like sha512 can be specified. Both operations return Base64-encoded strings. ```php use Acquia\Hmac\Digest\Digest; $digest = new Digest(); // defaults to sha256; pass any hash_hmac algorithm name // Sign a message: returns base64(HMAC-SHA256(message, base64_decode(secret))) $secret = base64_encode('my-raw-secret'); $signature = $digest->sign("GET\napi.example.com\n/v1/items\n\n...", $secret); echo $signature; // Base64-encoded HMAC-SHA256 // Hash a request body: returns base64(sha256(body)) $bodyHash = $digest->hash('{"color":"blue"}'); echo $bodyHash; // used as X-Authorization-Content-SHA256 // Custom algorithm (e.g., sha512): $sha512Digest = new Digest('sha512'); $sig512 = $sha512Digest->sign('message', $secret); ``` -------------------------------- ### ResponseAuthenticator Source: https://context7.com/acquia/http-hmac-php/llms.txt Verifies the X-Server-Authorization-HMAC-SHA256 header on a response received by the client, confirming the server used the same key to sign it. ```APIDOC ## ResponseAuthenticator — Verifying Incoming Responses (Client Side) `ResponseAuthenticator` verifies the `X-Server-Authorization-HMAC-SHA256` header on a response received by the client, confirming the server used the same key to sign it. ```php use Acquia\Hmac\Key; use Acquia\Hmac\ResponseAuthenticator; use Acquia\Hmac\Exception\MalformedResponseException; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); // $signedRequest — the PSR-7 request that was sent (after RequestSigner added its headers) // $response — the PSR-7 response received from the server $authenticator = new ResponseAuthenticator($signedRequest, $key); try { if ($authenticator->isAuthentic($response)) { echo 'Response verified — server identity confirmed.'; } else { echo 'Response signature mismatch.'; } } catch (MalformedResponseException $e) { // Response is missing X-Server-Authorization-HMAC-SHA256 echo 'Malformed response: ' . $e->getMessage(); } ``` ``` -------------------------------- ### RequestSigner - Signing Outgoing PSR-7 Requests Source: https://context7.com/acquia/http-hmac-php/llms.txt `RequestSigner` adds the `X-Authorization-Timestamp`, `X-Authorization-Content-SHA256` (for requests with a body), and `Authorization` headers to a PSR-7 request. The returned request is immutable. ```APIDOC ## RequestSigner — Signing Outgoing PSR-7 Requests ### Description `RequestSigner` adds the `X-Authorization-Timestamp`, `X-Authorization-Content-SHA256` (for requests with a body), and `Authorization` headers to a PSR-7 request. The returned request is immutable (PSR-7 clone); the original is not modified. ### Usage ```php use Acquia\Hmac\Key; use Acquia\Hmac\RequestSigner; use Nyholm\Psr7\Request; use Nyholm\Psr7\Stream; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); $signer = new RequestSigner($key, 'MyRealm'); // second arg is the API realm // Build a PSR-7 request (any PSR-7 implementation works) $request = new Request('GET', 'https://api.example.com/v1/widgets'); $signedRequest = $signer->signRequest($request); // Inspect the added headers: echo $signedRequest->getHeaderLine('X-Authorization-Timestamp'); // Unix timestamp echo $signedRequest->getHeaderLine('Authorization'); // acquia-http-hmac realm="MyRealm",id="e7fe97fa...",nonce="...",version="2.0",headers="",signature="..." // POST request with a JSON body — X-Authorization-Content-SHA256 is added automatically: $body = Stream::create('{"widget":{"color":"blue"}}'); $postRequest = new Request('POST', 'https://api.example.com/v1/widgets', ['Content-Type' => 'application/json'], $body); $signedPost = $signer->signRequest($postRequest); echo $signedPost->getHeaderLine('X-Authorization-Content-SHA256'); // base64(sha256(body)) // Sign with additional custom headers included in the HMAC signature: $customRequest = new Request('GET', 'https://api.example.com/v1/items', [ 'X-Custom-Realm' => 'widgets', 'X-Request-ID' => 'abc-123', ]); $signedCustom = $signer->signRequest($customRequest, ['X-Custom-Realm', 'X-Request-ID']); ``` ``` -------------------------------- ### Sign Outgoing PSR-7 Requests (PHP) Source: https://context7.com/acquia/http-hmac-php/llms.txt Adds X-Authorization-Timestamp, X-Authorization-Content-SHA256 (for requests with a body), and Authorization headers to a PSR-7 request. The returned request is immutable. ```php use Acquia\Hmac\Key; use Acquia\Hmac\RequestSigner; use Nyholm\Psr7\Request; use Nyholm\Psr7\Stream; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); $signer = new RequestSigner($key, 'MyRealm'); // second arg is the API realm // Build a PSR-7 request (any PSR-7 implementation works) $request = new Request('GET', 'https://api.example.com/v1/widgets'); $signedRequest = $signer->signRequest($request); // Inspect the added headers: echo $signedRequest->getHeaderLine('X-Authorization-Timestamp'); // Unix timestamp echo $signedRequest->getHeaderLine('Authorization'); // acquia-http-hmac realm="MyRealm",id="e7fe97fa...",nonce="...",version="2.0",headers="",signature="..." // POST request with a JSON body — X-Authorization-Content-SHA256 is added automatically: $body = Stream::create('{"widget":{"color":"blue"}}'); $postRequest = new Request('POST', 'https://api.example.com/v1/widgets', ['Content-Type' => 'application/json'], $body); $signedPost = $signer->signRequest($postRequest); echo $signedPost->getHeaderLine('X-Authorization-Content-SHA256'); // base64(sha256(body)) // Sign with additional custom headers included in the HMAC signature: $customRequest = new Request('GET', 'https://api.example.com/v1/items', [ 'X-Custom-Realm' => 'widgets', 'X-Request-ID' => 'abc-123', ]); $signedCustom = $signer->signRequest($customRequest, ['X-Custom-Realm', 'X-Request-ID']); ``` -------------------------------- ### Symfony Service Configuration for HMAC Authentication Source: https://context7.com/acquia/http-hmac-php/llms.txt Configure services in Symfony for HMAC authentication, including KeyLoader, RequestAuthenticator, AuthenticationEntryPoint, and AuthenticationProvider. Optionally, configure HmacResponseListener for signing responses. ```yaml # config/services.yaml services: hmac.keyloader: class: Acquia\Hmac\KeyLoader arguments: $keys: { 'e7fe97fa-a0c8-4a42-ab8e-2c26d52df059': '%env(base64:HMAC_SECRET)%' } hmac.request.authenticator: class: Acquia\Hmac\RequestAuthenticator arguments: ['@hmac.keyloader'] hmac.entry_point: class: Acquia\Hmac\Symfony\HmacAuthenticationEntryPoint hmac.security.authentication.provider: class: Acquia\Hmac\Symfony\HmacAuthenticationProvider arguments: ['@hmac.request.authenticator'] hmac.security.authentication.listener: class: Acquia\Hmac\Symfony\HmacAuthenticationListener arguments: - '@security.token_storage' - '@security.authentication.manager' - '@hmac.entry_point' # Optionally sign responses: hmac.response_listener: class: Acquia\Hmac\Symfony\HmacResponseListener tags: - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse } # config/packages/security.yaml security: firewalls: api: pattern: ^/api/ stateless: true hmac_auth: true ``` -------------------------------- ### Authenticate PSR-7 requests and sign responses Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Use RequestAuthenticator to verify incoming PSR-7 requests and ResponseSigner to sign outgoing PSR-7 responses. A KeyLoader implementation is required for the authenticator. ```php use Acquia\Hmac\RequestAuthenticator; use Acquia\Hmac\ResponseSigner; // $keyLoader implements \Acquia\Hmac\KeyLoaderInterface $authenticator = new RequestAuthenticator($keyLoader); // $request implements PSR-7's \Psr\Http\Message\RequestInterface // An exception will be thrown if it cannot authenticate. $key = $authenticator->authenticate($request); $signer = new ResponseSigner($key, $request); $signedResponse = $signer->signResponse($response); ``` -------------------------------- ### AuthorizationHeaderBuilder Source: https://context7.com/acquia/http-hmac-php/llms.txt Constructs the `Authorization` header value from its constituent parts, offering fine-grained control for custom HTTP layer integrations. ```APIDOC ## AuthorizationHeaderBuilder ### Description Constructs the `Authorization` header value from its constituent parts (realm, key ID, nonce, version, custom headers, and computed signature). Use this directly when you need fine-grained control or are integrating with a custom HTTP layer. ### Usage ```php use Acquia\Hmac\AuthorizationHeaderBuilder; use Acquia\Hmac\Key; use Nyholm\Psr7\Request; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); $request = (new Request('GET', 'https://api.example.com/v1/items')) ->withHeader('X-Authorization-Timestamp', (string) time()); $builder = new AuthorizationHeaderBuilder($request, $key); $builder->setRealm('MyRealm'); $builder->setId($key->getId()); $builder->setCustomHeaders(['X-Custom-Header']); $authHeader = $builder->getAuthorizationHeader(); echo $authHeader->getRealm(); // MyRealm echo $authHeader->getId(); // e7fe97fa-a0c8-4a42-ab8e-2c26d52df059 echo $authHeader->getNonce(); // auto-generated v4 UUID echo $authHeader->getVersion(); // 2.0 echo $authHeader->getSignature(); // base64-encoded HMAC-SHA256 // Cast to string for direct use in an Authorization header: echo (string) $authHeader; // acquia-http-hmac realm="MyRealm",id="e7fe97fa...",nonce="",version="2.0",headers="",signature="..." ``` ``` -------------------------------- ### Register HMAC Listener in Symfony Bundle Source: https://github.com/acquia/http-hmac-php/blob/master/README.md Extend your application's bundle to register the HmacFactory. This ensures the HMAC security listener is available within the Symfony security system. ```php namespace AppBundle; use Acquia\Hmac\Symfony\HmacFactory; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; class AppBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $extension = $container->getExtension('security'); $extension->addSecurityListenerFactory(new HmacFactory()); } } ``` -------------------------------- ### Symfony Security Integration with HmacFactory Source: https://context7.com/acquia/http-hmac-php/llms.txt Integrate HMAC authentication into a Symfony application by extending the Bundle class and adding the HmacFactory to the security extension. This automatically handles HMAC authentication for protected firewalls. ```php // src/AppBundle/AppBundle.php namespace AppBundle; use Acquia\Hmac\Symfony\HmacFactory; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; class AppBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->getExtension('security') ->addSecurityListenerFactory(new HmacFactory()); } } ``` -------------------------------- ### Authenticate Incoming PSR-7 Requests Source: https://context7.com/acquia/http-hmac-php/llms.txt Validates the Authorization header, checks the timestamp, and compares signatures for incoming PSR-7 requests. Requires a KeyLoaderInterface to load keys and throws specific exceptions for different failure types. ```php use Acquia\Hmac\KeyLoader; use Acquia\Hmac\RequestAuthenticator; use Acquia\Hmac\Exception\InvalidSignatureException; use Acquia\Hmac\Exception\KeyNotFoundException; use Acquia\Hmac\Exception\MalformedRequestException; use Acquia\Hmac\Exception\TimestampOutOfRangeException; // Provide all known keys (id => Base64-encoded secret) $keyLoader = new KeyLoader(['e7fe97fa-a0c8-4a42-ab8e-2c26d52df059' => base64_encode('secret')]); $authenticator = new RequestAuthenticator($keyLoader); // $request is a PSR-7 RequestInterface arriving at your API endpoint try { $key = $authenticator->authenticate($request); // Request is authentic — $key->getId() identifies the caller echo 'Authenticated as: ' . $key->getId(); } catch (TimestampOutOfRangeException $e) { // Clock skew > 15 min: replay-attack protection triggered http_response_code(401); echo 'Timestamp error: ' . $e->getMessage(); // "Request is too old" / "Request is too far in the future" } catch (KeyNotFoundException $e) { http_response_code(401); echo 'Unknown key: ' . $e->getMessage(); } catch (InvalidSignatureException $e) { http_response_code(403); echo 'Bad signature: ' . $e->getMessage(); } catch (MalformedRequestException $e) { http_response_code(400); echo 'Malformed request: ' . $e->getMessage(); } ``` -------------------------------- ### Guzzle HmacAuthMiddleware Integration Source: https://context7.com/acquia/http-hmac-php/llms.txt Use this middleware to automatically sign outgoing Guzzle requests and verify incoming response signatures. It requires a Key object and can optionally include custom headers in the signature. ```php use Acquia\Hmac\Key; use Acquia\Hmac\Guzzle\HmacAuthMiddleware; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Exception\ClientException; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); // Optional: list custom header names to include in the HMAC signature $customHeaders = ['X-Custom-1', 'X-Custom-2']; $middleware = new HmacAuthMiddleware($key, 'MyRealm', $customHeaders); $stack = HandlerStack::create(); $stack->push($middleware); $client = new Client(['handler' => $stack]); try { $response = $client->get('https://api.example.com/v1/widgets', [ 'headers' => [ 'X-Custom-1' => 'value1', 'X-Custom-2' => 'value2', ], ]); echo $response->getBody(); // response has been signature-verified automatically } catch (ClientException $e) { // 401 responses pass through without signature verification echo $e->getResponse()->getStatusCode(); } catch (\Acquia\Hmac\Exception\MalformedResponseException $e) { // Server response signature failed verification echo 'Response not authentic: ' . $e->getMessage(); } // POST with a JSON body: $response = $client->post('https://api.example.com/v1/widgets', [ 'headers' => ['Content-Type' => 'application/json'], 'body' => json_encode(['color' => 'blue']), ]); ``` -------------------------------- ### RequestAuthenticator Source: https://context7.com/acquia/http-hmac-php/llms.txt Validates the Authorization header of an incoming PSR-7 request, checks the timestamp, loads the key, and compares signatures. Returns the matching Key on success or throws a typed exception on failure. ```APIDOC ## RequestAuthenticator — Authenticating Incoming PSR-7 Requests (Server Side) `RequestAuthenticator` validates the `Authorization` header of an incoming PSR-7 request, checks the timestamp is within ±15 minutes, loads the key by ID using a `KeyLoaderInterface`, and compares signatures using a timing-safe comparison. Returns the matching `Key` on success; throws a typed exception on failure. ```php use Acquia\Hmac\KeyLoader; use Acquia\Hmac\RequestAuthenticator; use Acquia\Hmac\Exception\InvalidSignatureException; use Acquia\Hmac\Exception\KeyNotFoundException; use Acquia\Hmac\Exception\MalformedRequestException; use Acquia\Hmac\Exception\TimestampOutOfRangeException; // Provide all known keys (id => Base64-encoded secret) $keyLoader = new KeyLoader(['e7fe97fa-a0c8-4a42-ab8e-2c26d52df059' => base64_encode('secret')]); $authenticator = new RequestAuthenticator($keyLoader); // $request is a PSR-7 RequestInterface arriving at your API endpoint try { $key = $authenticator->authenticate($request); // Request is authentic — $key->getId() identifies the caller echo 'Authenticated as: ' . $key->getId(); } catch (TimestampOutOfRangeException $e) { // Clock skew > 15 min: replay-attack protection triggered http_response_code(401); echo 'Timestamp error: ' . $e->getMessage(); // "Request is too old" / "Request is too far in the future" } catch (KeyNotFoundException $e) { http_response_code(401); echo 'Unknown key: ' . $e->getMessage(); } catch (InvalidSignatureException $e) { http_response_code(403); echo 'Bad signature: ' . $e->getMessage(); } catch (MalformedRequestException $e) { http_response_code(400); echo 'Malformed request: ' . $e->getMessage(); } ``` ``` -------------------------------- ### Sign Outgoing PSR-7 Responses Source: https://context7.com/acquia/http-hmac-php/llms.txt Appends an X-Server-Authorization-HMAC-SHA256 header to a PSR-7 response, allowing clients to verify the server's identity. Requires the authenticated key and the original signed request. ```php use Acquia\Hmac\ResponseSigner; use Nyholm\Psr7\Response; use Nyholm\Psr7\Stream; // $key — the Key returned by RequestAuthenticator::authenticate() // $request — the original PSR-7 request (already bearing Authorization header) $signer = new ResponseSigner($key, $request); $body = Stream::create('{"widget":{"id":42,"color":"blue"}}'); $response = new Response(200, ['Content-Type' => 'application/json'], $body); $signedResponse = $signer->signResponse($response); echo $signedResponse->getHeaderLine('X-Server-Authorization-HMAC-SHA256'); // base64-encoded HMAC-SHA256 of (nonce + "\n" + timestamp + "\n" + responseBody) ``` -------------------------------- ### Parsing Authorization Headers from PSR-7 Requests Source: https://context7.com/acquia/http-hmac-php/llms.txt Parses an existing `Authorization` header from an incoming PSR-7 request into a structured `AuthorizationHeader` object. This is useful for verifying incoming requests. It can throw a `MalformedRequestException` if the header is missing or malformed. ```php use Acquia\Hmac\AuthorizationHeader; use Acquia\Hmac\Exception\MalformedRequestException; // $request is a PSR-7 RequestInterface with an Authorization header already set try { $authHeader = AuthorizationHeader::createFromRequest($request); echo $authHeader->getRealm(); // e.g. "MyRealm" echo $authHeader->getId(); // key ID echo $authHeader->getNonce(); // UUID nonce echo $authHeader->getVersion(); // "2.0" echo $authHeader->getSignature(); // Base64 signature print_r($authHeader->getCustomHeaders()); // ['X-Custom-1', 'X-Custom-2'] } catch (MalformedRequestException $e) { // Authorization header missing or malformed (missing realm/id/nonce/version/signature) echo 'Parse error: ' . $e->getMessage(); } ``` -------------------------------- ### Guzzle HmacAuthMiddleware Source: https://context7.com/acquia/http-hmac-php/llms.txt This middleware automatically signs outgoing requests and verifies incoming response signatures with Guzzle, eliminating the need for manual signing in application code. ```APIDOC ## Guzzle HmacAuthMiddleware ### Description This middleware automatically signs outgoing requests and verifies incoming response signatures with Guzzle, eliminating the need for manual signing in application code. ### Usage ```php use Acquia\Hmac\Key; use Acquia\Hmac\Guzzle\HmacAuthMiddleware; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Exception\ClientException; $key = new Key('e7fe97fa-a0c8-4a42-ab8e-2c26d52df059', base64_encode('secret')); // Optional: list custom header names to include in the HMAC signature $customHeaders = ['X-Custom-1', 'X-Custom-2']; $middleware = new HmacAuthMiddleware($key, 'MyRealm', $customHeaders); $stack = HandlerStack::create(); $stack->push($middleware); $client = new Client(['handler' => $stack]); try { $response = $client->get('https://api.example.com/v1/widgets', [ 'headers' => [ 'X-Custom-1' => 'value1', 'X-Custom-2' => 'value2', ], ]); echo $response->getBody(); // response has been signature-verified automatically } catch (ClientException $e) { // 401 responses pass through without signature verification echo $e->getResponse()->getStatusCode(); } catch (\Acquia\Hmac\Exception\MalformedResponseException $e) { // Server response signature failed verification echo 'Response not authentic: ' . $e->getMessage(); } // POST with a JSON body: $response = $client->post('https://api.example.com/v1/widgets', [ 'headers' => ['Content-Type' => 'application/json'], 'body' => json_encode(['color' => 'blue']), ]); ``` ``` -------------------------------- ### Retrieving Authenticated Key in Symfony Controller Source: https://context7.com/acquia/http-hmac-php/llms.txt Access the authenticated HMAC key within a Symfony controller by retrieving it from the request attributes. The key is an instance of Acquia\Hmac\KeyInterface, and its ID can be used to identify the caller. ```php // In a controller — retrieve the authenticated key from the request attributes: use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; class WidgetController extends AbstractController { public function list(Request $request): JsonResponse { // The authenticated Key is stored in request attributes by the listener $key = $request->attributes->get('hmac.key'); // $key is an Acquia\Hmac\KeyInterface — use $key->getId() to identify the caller return $this->json(['widgets' => [], 'caller' => $key->getId()]); } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.