Authenticating to Services by storing credentials in Azure Key Vault

Almost all the time we face this challenge of maintaining a central repository for storing the credentials, certificates and access keys to authenticate to the various online services. In this blog post I am going to cover various authentication scenarios that can be achieved by storing credentials and certificates in Azure Key Vault and how you can use them to authenticate your services like Azure, Office 365 or any other OAuth/Service provider.

The two major types of credentials we use for authenticating using Key Vault are:

  1. Username and Password OR Client ID and Client Secret for Azure Service Principal authentication
  2. Certificate authentication

The way this will work is that your code will call the Key Vault service to fetch the credential you wish to retrieve. Once key vault returns this, you can use this in your code for further authenticating to your services. The pre-requisite for this is that you have a key vault created in azure and that you grant appropriate access to the vault to your service principal/user in the context of which your code will connect to Azure Key Vault. Lets assume for this demo that your vault name is "RohitMinni".

Username and Password OR Client ID and Client Secret Authentication

Login to the azure portal and create secrets that represent "username" and "password" as shown in the below figure. Give the appropriate username and password values when you create the secrets. In this example it is o365username and o365password

img1

 

Certificate Authentication

Run the following PowerShell code to create and upload a certificate in your Key Vault. Also make sure to upload the .cer file as a management certificate in the Azure Management Portal (https://manage.windowsazure.com).

$cert = New-SelfSignedCertificate -DnsName TestCertificate -CertStoreLocation "cert:\LocalMachine\My" $certName = "testcertificate" $passwordText = "pass@word1" $vaultName = "vaultname" $password = ConvertTo-SecureString -String $passwordText -Force -AsPlainText Export-PfxCertificate -Cert $cert -FilePath ".\$certName.pfx" -Password $password Export-Certificate -Type CERT -Cert $cert -FilePath ".\$certName.cer" $servicePrincipalSecpasswd = ConvertTo-SecureString "Client Secret" -AsPlainText -Force $servicePrincialCred = New-Object System.Management.Automation.PSCredential ("ClientID@yourdomain.onmicrosoft.com", $servicePrincipalSecpasswd) $TenantID = "Tenant ID" Login-AzureRmAccount -ServicePrincipal -Credential $servicePrincialCred -TenantId $TenantID cd $env:TEMP $pfxFilePath = '.\$certName.pfx' $flag = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $collection.Import($pfxFilePath, $passwordText, $flag) $pkcs12ContentType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12 #$clearBytes = $collection.Export($pkcs12ContentType) $clearBytes = $collection.Export($pkcs12ContentType,$pwd) $fileContentEncoded = [System.Convert]::ToBase64String($clearBytes) $secret = ConvertTo-SecureString -String $fileContentEncoded -AsPlainText –Force $secretContentType = 'application/x-pkcs12' Set-AzureKeyVaultSecret -VaultName $vaultName -Name 'certificate' -SecretValue $Secret -ContentType $secretContentType img2

 

Now that we have the basic setup completed, we will get to the part where we use these credentials in some C# code.

Please find below some code snippets that would allow you to connect to your Azure Key Vault, fetch the credentials you just stored and use them to interact with some Azure or Office 365 services to consume their REST API's or Client Libraries.

 public static async Task<string> GetToken(string authority, string resource, string scope)
{
 
    var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
    ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ClientId"],
    ConfigurationManager.AppSettings["ClientSecret"]);
    AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
 
    if (result == null)
        throw new InvalidOperationException("Failed to obtain the JWT token");
 
    return result.AccessToken;
 
}
 
private static IEnumerable<SecretItem> FetchKeyVaultSecrets(string vaultName)
{
 
    try
    {
 
        keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
        var secrets = keyVaultClient.GetSecretsAsync("https://" + vaultName + ".vault.azure.net:443").Result.Value;
        return secrets;
 
    }
    catch (Exception ex)
    {
 
        return null;
 
    }
 
}
 
private static async void ConnectToServices()
{
 
    var secrets = FetchKeyVaultSecrets("RohitMinni"); //Fetch Secrets from the Vault
    var certificate = await FetchKeyVaultCredential(secrets, "certificate") as X509Certificate2; //Gets the uploaded certificate from the vault with password if any
    var subscriptionId = await FetchKeyVaultCredential(secrets, "subscriptionid") as string;
    var cloudServices = await GetCloudService(subscriptionId, certificate);
 
    /*
 
    var o365Username = await FetchKeyVaultCredential(secrets, "o365username") as string;
    var o365Password = await FetchKeyVaultCredential(secrets, "o365password") as string;
 
    You can write custom code here to authenticate using this username and password
 
    */
 
}
 
public async static Task<List<string>> GetCloudService(string subscriptionId, X509Certificate2 certificate)
{
 
    HostedServiceListResponse response;
    List<string> cloudServices;
    try
    {
 
        Microsoft.WindowsAzure.SubscriptionCloudCredentials credential = new Microsoft.WindowsAzure.CertificateCloudCredentials(subscriptionId, certificate);
        using (var computeClient = new ComputeManagementClient(credential))
        {
 
            response = await computeClient.HostedServices.ListAsync();
            cloudServices = response.HostedServices.Select(s => s.ServiceName).ToList();
 
        }
        return cloudServices;
 
    }
    catch (Exception ex)
    {
 
        return null;
 
    }
 
}

 private static async Task<Object> FetchKeyVaultCredential(IEnumerable<SecretItem> secrets, string secretType)
 {
 try
 {
 Secret localSecret = null;
 var secretId = secrets.Where(a => a.Identifier.Name.Contains(secretType)).FirstOrDefault();
 string certificatePassword = "";
 if (secretType == "certificate")
 {
 localSecret = await keyVaultClient.GetSecretAsync(secretId.Identifier.Identifier);
 var Password = localSecret.Value;

 //Extract associated certificate password if any
 var secretPasswordId = secrets.Where(a => a.Identifier.Name.Contains("certificatepassword")).FirstOrDefault();
 if(secretPasswordId != null)
 {
 localSecret = await keyVaultClient.GetSecretAsync(secretPasswordId.Identifier.Identifier); 
 certificatePassword = localSecret.Value;
 }
 var certificate = new X509Certificate2(Convert.FromBase64String(Password), certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
 return certificate;
 }
 else if(secretType == "subscriptionid")
 {
 localSecret = await keyVaultClient.GetSecretAsync(secretId.Identifier.Identifier);
 var Password = localSecret.Value;
 return Password;
 }
 else if (secretType == "subscriptionname")
 {
 localSecret = await keyVaultClient.GetSecretAsync(secretId.Identifier.Identifier);
 var Password = localSecret.Value;
 return Password;
 }
 }
 catch(Exception ex)
 {
 return null;
 }
 return null;
 }