symfony / azure-keyvault-key-management
Symfony Azure Key Vault Key Management Bridge
Package info
github.com/symfony/azure-keyvault-key-management
Type:symfony-key-management-bridge
pkg:composer/symfony/azure-keyvault-key-management
Requires
- php: >=8.4.1
- symfony/http-client: ^7.4|^8.0
- symfony/key-management: ^8.2
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 12:39:01 UTC
README
Provides an implementation of Symfony\Component\KeyManagement\EncrypterInterface,
Symfony\Component\KeyManagement\DecrypterInterface and
Symfony\Component\KeyManagement\DataKeyGeneratorInterface backed by
Azure Key Vault (and
Managed HSM) over
its REST API. Encryption, decryption and data-key wrapping never expose the
master key: Azure performs them server-side.
This Bridge is experimental. Experimental features are not covered by Symfony's Backward Compatibility Promise.
use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\KeyManagement\Bridge\AzureKeyVault\AzureKeyVault; use Symfony\Component\KeyManagement\Bridge\AzureKeyVault\ClientCredentialsTokenProvider; $client = HttpClient::createForBaseUri('https://my-vault.vault.azure.net/'); $tokens = new ClientCredentialsTokenProvider( $client, $_SERVER['AZURE_TENANT_ID'], $_SERVER['AZURE_CLIENT_ID'], $_SERVER['AZURE_CLIENT_SECRET'], ); $kms = new AzureKeyVault($client, $tokens); // Use the key name for the latest version on writes, or append `/<version>` to select one. $ciphertext = $kms->encrypt('app-key', 'hello world'); $plaintext = $kms->decrypt($ciphertext); $dataKey = $kms->generateDataKey('app-key', 32); $result = $dataKey->use(fn (string $dek): string => /* local AEAD encrypt */);
Each new ciphertext and wrapped data key records the key version it was produced under, and the bridge checks that Azure's response names the requested key. Keep older key versions enabled while their ciphertexts or wrapped data keys must remain readable.
Authentication
Bring your own TokenProviderInterface to plug
any Azure AD flow: the bundled ClientCredentialsTokenProvider covers the
client_credentials grant (tenant id + client id + client secret) and caches
the token in memory until 60 seconds before its advertised expiration.
Managed Identity, Workload Identity, federated credentials, on-behalf-of, ...
are out of scope for the default provider; implement
TokenProviderInterface for your authentication flow.
Custom token providers implement invalidateToken($token) to discard a cached
token only when it matches the rejected value. If Key Vault responds with HTTP
401, the bridge obtains a new token and retries the request once if that token
differs. HTTP 403 is reported directly without invalidating the token.
Algorithms
The bridge accepts two configurable algorithms:
encryptAlgorithm(defaultRSA-OAEP-256): selects the algorithm forencrypt(). The AEAD variantsA128GCM/A192GCM/A256GCMare also accepted. These require a symmetric key, available on Managed HSM or Key Vault Premium in preview.wrapAlgorithm(defaultRSA-OAEP-256): selects the algorithm forgenerateDataKey()andwrapDataKey(). The same algorithms are accepted; with a symmetric key you can also selectA128KW/A192KW/A256KWexplicitly.
The constructor and DSN factory reject any other algorithm.
RSA-OAEP and RSA1_5 are excluded following Microsoft's recommendations.
CBC variants are excluded because the bridge does not preserve their IV and they do not authenticate ciphertexts.
AES-KW is accepted only for data-key wrapping.
Every blob records its algorithm, so a configuration change only affects new writes. Reads use the recorded algorithm, subject to the same payload or wrapping allowlist. Unprefixed blobs and malformed algorithm prefixes are rejected.
encrypt() / decrypt() go through the RSA path by default and are therefore
suited to small payloads (config secrets, tokens). For arbitrary-size
payloads, use Symfony\Component\KeyManagement\EnvelopeEncrypter on top of the bridge:
the master key only sees the wrapped DEK.
DSN scheme
azure-keyvault://<clientId>:<clientSecret>@<vault-name>.vault.azure.net?tenant=<tenantId>[&algorithm=...&wrap_algorithm=...&api_version=...&audience=...&timeout=...&max_duration=...]
The host is the full vault DNS (<name>.vault.azure.net, or
<name>.managedhsm.azure.net for Managed HSM). The factory selects a Key Vault
or Managed HSM audience from the host, which audience can override, and it
always authenticates against the public Microsoft Entra authority. A sovereign
cloud (US government, China) needs another authority, so construct
AzureKeyVault there manually with a token provider configured for that cloud.
The timeout (idle timeout) and max_duration (whole request) options, in
seconds, set the HTTP client's timeouts, for the token requests too. When the
factory is given no client, it builds one with a 5-second timeout and a
10-second max_duration. A client given to the factory, like the application's
http_client, keeps its own settings unless the DSN sets the timeouts.
Requests don't follow redirects, whatever the client. On a 307 or 308 redirect
to another host, the client secret or the plaintext would be sent again.
Examples:
azure-keyvault://CLIENT_ID:CLIENT_SECRET@my-vault.vault.azure.net?tenant=TENANT_ID
azure-keyvault://CLIENT_ID:CLIENT_SECRET@my-vault.vault.azure.net?tenant=TENANT_ID&algorithm=RSA-OAEP-256&api_version=7.4
azure-keyvault://CLIENT_ID:CLIENT_SECRET@my-hsm.managedhsm.azure.net?tenant=TENANT_ID&algorithm=A256GCM&wrap_algorithm=A256KW
AAD support
AEAD algorithms (A128GCM/A192GCM/A256GCM) accept Azure's aad
parameter natively and are integrity-protected.
RSA and AES-KW have no AAD support; passing a non-empty $aad with either algorithm raises UnsupportedOperationException.
For AEAD ciphertexts and wrapped keys, the blob is <alg>.<iv>.<tag>.<value>.
For RSA ciphertexts and RSA or AES-KW wrapped keys, it is <alg>.<value>.
The IV, tag and value are the base64url fields returned by Azure; the algorithm prefix is added by the bridge and removed before sending a read request.
Callers store the complete opaque blob, together with its key ID.