Migrating to the new C# Azure KeyVault SDK Libraries
You may be familiar with the Microsoft.Azure.KeyVault SDK. This SDK is being retired in favor of 3 new SDKs:
As you can see, the Microsoft Azure SDK team split the KeyVault functionality in 3 distinct SDKs. All those SDKs are unified with the Azure.Identity SDK to manage authentication. Let’s deep dive a little bit into those SDKs.
I wanted to brush up on those, as usually what people do, when they have the KeyVault setup in their application, they tend to forget about it. If you want to migrate to the new SDKs (or you’re looking to consume the KeyVault through code), this post can be of interest to you.
Azure.Identity SDK
The Azure Identity library is the base library that provides Azure Active Directory token authentication support across the Azure SDK. It provides a set of TokenCredential implementations which can be used to construct Azure SDK clients which support AAD token authentication1. It provides some implementations to support most scenarios used in today’s development.
Here’s a summary table of all the authentication implementations.
Credential | Description | Notes |
---|---|---|
Authenticating Azure Hosted Applications | ||
DefaultAzureCredential | Provides a simplified authentication experience to quickly start developing applications run in the Azure cloud | Can be configured to use the environment variables. See the definition here |
ChainedTokenCredential | Allows users to define custom authentication flows composing multiple credentials | |
ManagedIdentityCredential | Authenticates the managed identity of an azure resource | |
EnvironmentCredential | Authenticates a service principal or user via credential information specified in environment variables | Uses environment variables. See the definition here |
Authenticating Service Principals | ||
ClientSecretCredential | Authenticates a service principal using a secret | |
ClientSecretCredential | Authenticates a service principal using a certificate | |
Authenticating Users | ||
InteractiveBrowserCredential | Interactively authenticates a user with the default system browser | |
DeviceCodeCredential | Interactively authenticates a user on devices with limited UI | |
UsernamePasswordCredential | Authenticates a user with a username and password | |
AuthorizationCodeCredential | Authenticate a user with a previously obtained authorization code | |
Authenticating via Development Tools | ||
AzureCliCredential | Authenticate in a development environment with the Azure CLI | |
VisualStudioCredential | Authenticate in a development environment with Visual Studio | |
VisualStudioCodeCredential | Authenticate in a development environment with Visual Studio Code |
All credential implementations in the Azure Identity library are threadsafe, and a single credential instance can be used by multiple service clients.
One implementation that I really enjoy is the ChainedTokenCredential. This class is super useful when you want to have multiple authentication mechanisms supported, for instance, if you’re building yourself a library. It can also be very useful in the cases when you want to support both development and production modes at the same time. Consider the following example to demonstrate the idea:
You want to use managed identity in production and fall back to environment variables if managed identity is not available. However, in development, you want the ability for your developers to authenticate through client credentials (client_id, client_secret). You may also want to enable the VisualStudio credentials if the DEBUG macro is available. You can do this using the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
TokenCredential tokenCredential; var credentials = new List<TokenCredential> { new ManagedIdentityCredential(), new EnvironmentCredential() }; #if DEBUG credentials.Add(new VisualStudioCredential()); credentials.Add(new VisualStudioCodeCredential()); #endif if (!string.IsNullOrWhiteSpace(tenantId) && !string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret)) { credentials.Add(new ClientSecretCredential(tenantId,clientId,clientSecret)); tokenCredential = new ChainedTokenCredential(credentials); } else { tokenCredential = new ChainedTokenCredential(new ManagedIdentityCredential(), new EnvironmentCredential()); } |
If you prefer to use the DefaultAzureCredential (which encapsulates most of the providers you see above), you can control which one to actually trigger. For that, instanciate the class with its options overload using the class DefaultAzureCredentialOptions.
1 2 3 4 5 6 7 8 9 10 11 |
var defaultAzureCredentialOptions = new DefaultAzureCredentialOptions(); defaultAzureCredentialOptions.ExcludeAzureCliCredential = true; defaultAzureCredentialOptions.ExcludeEnvironmentCredential = true; defaultAzureCredentialOptions.ExcludeInteractiveBrowserCredential = true; defaultAzureCredentialOptions.ExcludeManagedIdentityCredential = false; defaultAzureCredentialOptions.ExcludeSharedTokenCacheCredential = true; defaultAzureCredentialOptions.ExcludeVisualStudioCodeCredential = true; defaultAzureCredentialOptions.ExcludeVisualStudioCredential = true; // Actually only include ManagedIdentityCredential in DefaultAzureCredential. var credential = new DefaultAzureCredential(defaultAzureCredentialOptions); |
Along with the Azure.Security.KeyVault.* SDKs, this SDK is used in the following other Azure SDKs:
Azure.Storage.Blobs
Azure.Storage.Queues
Azure.Messaging.EventHubs
Azure.Security.KeyVault.* SDKs
Consuming the Azure.Security.KeyVault.* SDKs is pretty straight forward. Create yourself the authentication credentials you need using the Azure.Identity SDK and then create the appropriate implementations depending on what you want to consume.
All the examples below start with some credentials:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var tenantId = "<your_tenant_id>"; var clientId = "<your_client_id>"; var clientSecret = "<your_client_secret>"; TokenCredential tokenCredential; if (!string.IsNullOrWhiteSpace(tenantId) && !string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret)) { tokenCredential = new ChainedTokenCredential( new ClientSecretCredential(tenantId, clientId, clientSecret), new EnvironmentCredential(), new ManagedIdentityCredential()); } else { tokenCredential = new ChainedTokenCredential(new EnvironmentCredential(), new ManagedIdentityCredential()); } |
Example 1: creating a X509Certificate2
One thing I see people often looking to do is creating a X509Certificate2 from a certificate in KeyVault. Remember that the Certificate in the KeyVault, if you grab it from the certificate section, will only expose the public key, and not the private key.
1 2 3 4 5 6 7 |
var keyVaultUri = new Uri("https://<your_vault_name>.vault.azure.net"); var certificateName = "<your_certificate_name>"; var client = new SecretClient(vaultUri: keyVaultUri, credential: credentials); var certificate = await client.GetSecretAsync(certificateName); var certificateX509 = new X509Certificate2(Convert.FromBase64String(certificate.Value.Value)); |
Example 2: encrypting string data with a cryptographic key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var keyVaultUri = new Uri("https://<your_vault_name>.vault.azure.net"); var keyName = "<your_key_name>"; var client = new KeyClient(keyVaultUri, tokenCredential); var key = client.GetKey(keyName).Value; var cryptographyClient = new CryptographyClient(key.Id,tokenCredential); string value = "domstamand.com"; // encrypt var encryptedBytes = Encoding.UTF8.GetBytes(value); var encryptResult = await cryptographyClient.EncryptAsync(EncryptionAlgorithm.RsaOaep256, encryptedBytes); var encryptedValue = Convert.ToBase64String(encryptResult.Ciphertext); // decrypt var decryptedBytes = Convert.FromBase64String(value); var decryptResult = await cryptographyClient.DecryptAsync(EncryptionAlgorithm.RsaOaep256, decryptedBytes); var decryptedValue = Encoding.UTF8.GetString(decryptResult.Plaintext); |
Migrating to the new Azure.Security.KeyVault SDK
If you remember, the Microsoft.Azure.KeyVault library was the one used to deal with anything KeyVault. To interact with the KeyVault, you needed a keyVault client.
Here’s a sample on how you achieved that:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Connect using Managed Identity var azureServiceTokenProvider = new AzureServiceTokenProvider(); var authenticationCallback = new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback); var keyVaultClient = new KeyVaultClient(authenticationCallback); // Connect using clientId, clientSecret var keyVaultClient = new KeyVaultClient(async (authority, resource, scope) => { var aadCredential = new ClientCredential(clientId, clientSecret); var authenticationContext = new AuthenticationContext(authority, null); return (await authenticationContext.AcquireTokenAsync(resource, aadCredential)).AccessToken; }); |
If you wanted to grab a secret, you’d do await keyVaultClient.GetSecretAsync(secretName);
Now to do the same operation using the new SDK, you can do the following (assuming we use the default credentials):
1 2 3 4 5 6 |
string keyVaultUri = "<your_keyvault_uri>"; string secretName = "<your_secret_name>"; var client = new SecretClient(vaultUri: keyVaultUri, credential: new DefaultAzureCredential()); var secret = await client.GetSecretAsync(secretName); |
Conclusion
They are so much more goodies that you can get from the new Azure SDKs that I have not spoken about in this post. One of them is logging through diagnostics, that I really enjoy. Check out the new SDKs, they are worth it to simplify your development.