Today I’ll demo my new KeyVault proxy in the 425show. This page will allow you to follow along.
Check out the recording and if you have any questions, contact me on twitter @svrooij.
Badges
Resources
- What’s a JWT
- JWT.ms, Microsoft build JWT inspection website
- Azure AD client credentials
- MSAL.net Client assertions
- Source on Github
- Nuget package proxy
- Nuget package ConfidentialClientApplicationBuilderExtensions
What problem are you solving?
If you don’t protect the certificate that is used as a client credential, it’s just a complicated password. I wish that I have tight control over those certificates and making sure they cannot be exported. Client credentials should only be used if managed identities aren’t an option.
- Using client credentials in Insomnia or postman seems impossible.
- Having the certificate locally makes it vulnerable for stealing.
- Managed Identities cannot be used in multi-tenant applications.
Requirements
- Setup a KeyVault and generate a certificate in the KeyVault.
- Grant your user account access to
Sign
underKey permissions
>Cryptographic Operations
to the KeyVault - Create application registration, grant access to some application permission like
User.Read.All
, do admin consent and add the certificate under secrets.
Get started with KeyVault proxy (dev mode)
git checkout https://github.com/Smartersoft/identity-client-assertion.git
.\identity-client-assertion\Smartersoft.Identity.Client.Assertion.sln
Start the proxy (press F5) and try to open the swagger documentation of the proxy at /swagger/index.html
.
Get started with KeyVault proxy
dotnet tool install --global Smartersoft.Identity.Client.Assertion.Proxy
az-kv-proxy
Start the proxy and try to open the swagger documentation of the proxy at http://localhost:{port}/swagger/index.html
.
Demo
In the stream I showed the /api/Token/kv-certificate
endpoint. For this endpoint, you’ll need the following information:
clientId
You application id from AzuretenantId
The tenant IDscopes
Which scopes do you want a token for, it’s an array, but when using the client credentials, you should be specifying only one!keyVaultUri
The uri of the keyvaultcertificateName
The name of the certificate
This endpoint actually does two calls to the KeyVault, one to get the public part of the certificate (to generate the base64 url encoded hash and to get the keyUri), and one to the sign endpoint to actually sign the token. If you’re using this proxy more often use the api/Token/kv-key-info
endpoint to get the hash and the keyId once (they change only when you regenerate the certificate). And then use the api/Token/kv-key
endpoint to get your token faster.
In the demo I quickly mentioned the other endpoints. Just check-out the automatic openapi documentation on those endpoints at /swagger/index.html
.
How about production
As said in the demo (and the documentation), this proxy only suitable for use during development! If you want to use the same principal in production, you can check-out our ConfidentialClientApplicationBuilderExtensions. This small package provides a few extensions to the ConfidentialClientApplicationBuilder. You can then use the same goodies while using the Managed Identity in Azure to connect to the KeyVault.
Check-out this post for more details.
var tenantId = "28de03b2-dd2e-4c77-b76d-ac7aaebec43e";
var clientId = "20f8d4a4-f795-43c1-80fb-781a848f665f";
Uri keyId = new Uri("...."); // Fetch from KeyVault
var hash = "..."; // Use KeyVault proxy to find base64url encoded hash.
TokenCredential cred = new ManagedIdentityCredential(); // or 'new DefaultAzureCredential();' as in the proxy
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
//.WithCertificate(certificate)
.WithKeyVaultKey(tenantId, clientId, keyId, hash, cred)
//.WithRedirectUri(redirectUri )
.Build();
var tokenResult = await app
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
return tokenResult.AccessToken;
Look no secrets in the code! You should however make all those variables configurable, in a way you like. I’m not sure if the KeyId is supposed to be kept secret, but you can lockdown your KeyVault to be only accessible from specific IP’s or virtual networks so you’ll probably be fine.