In this article I will present my approach in accessing AAD user information using WebApi and a self signed certificate stored in keyvault. I am using .net 5, c#9 and for the test environmentI created an account using https://developer.microsoft.com/en-us/microsoft-365/dev-program , with this account you have a list of sample users which you can use to test your application.
Step 1 - Create a certificate
I am not using a real production ready certificate, and please don’t use this approach in production. I am creating a certificate for development purposes. For this I am using powershell(my version is 7).
This certificate was created and saved in the certificate store. Now we need to get it, we will locate and export it as a pfx file. In the export include also the private key, we will transform the certificate later.
To export the certificate, right click on it and under “all tasks” click “export”.
Great, now the pfx certificate is exported:
Step 2 - Extract the public certificate
To get the public key from the pfx file we can use power shell, I have to admit that I got stuck on the documentation and lost my patience. I used a more friendly (at least for me) library for this, openssl. To install openssl I used choco.
https://chocolatey.org/install
With choco you install openssl using the command : > choco install openssl
To extract the pem file, navigate to the folder where the certificate is stored and type:
Step 3 - AAD App Registration
Navigate to portal.azure.com and login with your test account credentials.
Under AAD we have to register our WebApi and provide it some permissions and upload the certificate public key.
In the registered app, under certificates and secrets upload the pem file
Now for my app I added some application (not delegated) permissions. Don’t forget to click the button “grant admin consent for”
Step 4 - KeyVault
Now we need to upload our certificate in the keyvault (in the same subscription).
For the keyvault the test account is not enough, I activated the free subscription (which provides 200$ free credits for first time use)
In the keyvault, under certificate we upload our certificate
In our example we access KV in 2 ways. By using the DefaultAzureCredentials when the app is deployed, and by using the AzureCliCredentials when in debug.
For the app to be allowed to use the KeyVault, we must provide it some permissions.
In KeyVault, under access policies add a new policy and for the principal select the app
Step 5 - Code Details
The important packages are
Azure.Identity Azure.Security.KeyVault.Certificates Azure.Security.KeyVault.Secrets Microsoft.Graph.Auth Newtonsoft.Json Microsoft.Graph
The following code shows a factory I use to create the graph client connection. In this example I am using a configuration value that I set in appsettings.json to determine whether I want the credentials to be from azure cli (allowing me to use the VS debugger) or the default identity (the app must be deployed)
Note: to login with azure cli open powershell (make sure you have az module installed) and type
az login
public class ConfidentialClientFactory : IGraphClientFactory { private readonly IConfigurationGraph configuration; public ConfidentialClientFactory(IConfigurationGraph configuration) { this.configuration = configuration; } public GraphServiceClient CreateClient() { // using Azure package instead of Microsoft.Azure // https://github.com/Azure/azure-sdk-for-net/blob/d9d93df6c75797a6027c125ab3ff61b2ac894102/sdk/keyvault/Azure.Security.KeyVault.Secrets/CHANGELOG.md#major-changes-from-microsoftazurekeyvault // https://stackoverflow.com/questions/59849541/azure-security-keyvault-secrets-vs-microsoft-azure-keyvault TokenCredential credentials = configuration.UseAzureCliCredentials ? new AzureCliCredential() // for debugging we login uzing azure cli to be able to access the KeyVault : new DefaultAzureCredential(); CertificateClient client = new CertificateClient( vaultUri: new Uri(configuration.KeyVaultUri), credential: credentials); SecretClient secretClient = new SecretClient( vaultUri: new Uri(configuration.KeyVaultUri), credential: credentials); Pageable versions = client.GetPropertiesOfCertificateVersions(configuration.CertificateName); X509Certificate2 x509Certificate2 = null; // https://stackoverflow.com/questions/37033073/how-can-i-create-an-x509certificate2-object-from-an-azure-key-vault-keybundle foreach (CertificateProperties certificate in versions) { if (!(certificate.Enabled ?? false) || certificate.ExpiresOn <= DateTimeOffset.UtcNow) { continue; } KeyVaultSecret certificateSecret = secretClient.GetSecret(certificate.Name, certificate.Version).Value; byte[] privateKey = Convert.FromBase64String(certificateSecret.Value); x509Certificate2 = new X509Certificate2(privateKey, (string)null, X509KeyStorageFlags.MachineKeySet); break; } if (x509Certificate2 is null) { throw new Exception("No valid certificate found"); } //https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#ClientCredentialsProvider IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder .Create(configuration.ClientId.ToString()) .WithTenantId(configuration.TenantId.ToString()) .WithCertificate(x509Certificate2) .Build(); ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication); // Create a new instance of GraphServiceClient with the authentication provider. return new GraphServiceClient(authProvider); } } }
Now we can use graph client to query over users:
IGraphServiceUsersCollectionPage usersPage = await graphClient.Users .Request() .Filter($"id eq '{userId}'") .Select("id, displayName,userPrincipalName, memberOf") .Expand("memberOf") .GetAsync();