Calling the Microsoft Graph, SharePoint Online, or other resource via an Azure AD Application is a fairly straightforward process when you use client ID + secret for the authentication mechanism. You can think of the client ID and secret as a username and password for authentication. Note that anyone who has that client ID + secret can log in as that Azure AD App and perform the actions that it has been granted. In an enterprise or secure environment certificate authentication is a more secure authentication option as it requires physically having the certificate which will only be deployed in a private fashion. In this post I’ll walk through how to deploy and leverage the necessary components to accomplish this. This example is part of a larger Azure Functions sample that I plan to release at a later date but the snippets below could be adapted for other hosting platforms.
Components Needed
- Certificate (self-signed or generated from a PKI-type infrastructure)
- Azure AD Application (using V1 in this example) with Microsoft Graph OAuth permissions
- Azure Function
Solution Overview
- Create certificate (self-signed in this example)
- Create Azure function
- Create Azure AD application registration
- Add certificate metadata to Azure AD application
- Deploy certificate to Azure Function certificate store
- Authenticate to Azure AD application using certificate
1) Create Certificate
If you are on Windows 8+ there is a PowerShell commandlet to create self-signed certificates easily. If not you’ll need to leverage MakeCert.exe or another certificate generating mechanism (ex. New-SelfSignedCertificateEx, documentation). Here is a sample of the PowerShell option.
# process for Windows 8+ type OS $ssc = New-SelfSignedCertificate -CertStoreLocation $CertificateStoreLocation -Provider $ProviderName ` -Subject "$CertificateSubject" -KeyDescription "$CertificateDescription" ` -NotBefore (Get-Date).AddDays(-1) -NotAfter (Get-Date).AddYears($CertificateNotAfterYears) ` -DnsName $CertificateDNSName -KeyExportPolicy Exportable # Export cert to PFX - uploaded to Azure App Service Export-PfxCertificate -cert cert:\CurrentUser\My\$($ssc.Thumbprint) -FilePath $certificatePFXPath -Password $CertificatePassword -Force # Export certificate - imported into the Service Principal Export-Certificate -Cert cert:\CurrentUser\My\$($ssc.Thumbprint) -FilePath $certificateCRTPath -Force
2) Create Azure Function
You can create an Azure Function from the Azure Portal (reference), Azure CLI (reference), or through tools / extensions built into Visual Studio 2017 (reference) / Visual Studio Code.
3-4) Create Azure AD Application and Add Certificate to Azure AD Application
Here is a sample for creating an Azure AD application using Azure PowerShell. In this example the certificate is added (-KeyCredentials) to the Azure AD application at time of creation, but it could also be added after the fact through the Azure Portal or PowerShell as well.
# prepare certificate for usage with creating AAD app $KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable, ` [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet, ` [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet $certFile = Get-ChildItem -Path $CertificatePFXPath $x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $x509.Import($certFile.FullName, $CertificatePassword, $KeyStorageFlags) $certValueRaw = $x509.GetRawCertData() $validFrom = $x509.NotBefore $validTo = $x509.NotAfter $keyId = [guid]::NewGuid() $keyCredential = New-Object -TypeName "Microsoft.Open.AzureAD.Model.KeyCredential" $keyCredential.StartDate = $validFrom $keyCredential.EndDate= $validTo $keyCredential.KeyId = $keyId $keyCredential.Type = "AsymmetricX509Cert" $keyCredential.Usage = "Verify" $keyCredential.Value = $certValueRaw $aadApp = New-AzureADApplication -DisplayName $AADAppName -Homepage $HomePage -ReplyUrls $ReplyUrls ` -IdentifierUris $IdentifierUri -KeyCredentials $keyCredential
5) Deploy certificate to Azure Function
While there is a native way to upload a certificate to an Azure App Service via the Azure CLI and the Azure Portal there is not a direct way via PowerShell. I was able to mimic an option with PowerShell by adding an SSL binding with a certificate and then immediately removing the SSL binding while not deleting the certificate (“-DeleteCertificate $false”). Below are examples for both options.
Note: In both examples below the password will be entered as cleartext instead of using a SecureString or other encrypted mechanism. This could pose a security risk but I haven’t found an alternative as of yet.
PowerShell
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($certificatePassword) $ClearTextPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) New-AzureRmWebAppSSLBinding -ResourceGroupName $resourceGroupName -WebAppName $webAppName -Name $webAppDNSName -CertificateFilePath (Get-ChildItem .\$certificatePFXPath) -CertificatePassword $ClearTextPassword Remove-AzureRmWebAppSSLBinding -ResourceGroupName $resourceGroupName -WebAppName $webAppName -Name $webAppDNSName -DeleteCertificate $false -Confirm:$false -Force
Azure CLI
az webapp config ssl upload --certificate-file "(certPath)" --certificate-password "(certPassword)" --name "(certName)" --resource-group "(resourceGroup)"
6) Authenticate to Azure AD application using certificate
The Azure Function code can authenticate to the Azure AD application using the certificate that was deployed in step 5. Below is a sample of the code used to retrieve the certificate. Since Azure Functions can be run locally or in Azure this will work locally if the certificate has been deployed to the certificate store or in Azure when deployed to the App Service.
public static X509Certificate2 GetCertificate(string thumbprint) { X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); try { store.Open(OpenFlags.ReadOnly); var col = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); if (col == null || col.Count == 0) { return null; } return col[0]; } finally { store.Close(); } }
Below is a sample of using the certificate to authenticate to SharePoint Online, but this could easily point to a different resource such as Microsoft Graph, Exchange Online, etc.
var url = Environment.GetEnvironmentVariable("tenantRootUrl"); var thumbprint = Environment.GetEnvironmentVariable("certificateThumbprint"); var resourceUri = Environment.GetEnvironmentVariable("resourceUri"); var authorityUri = Environment.GetEnvironmentVariable("authorityUri"); var clientId = Environment.GetEnvironmentVariable("clientId"); var ac = new AuthenticationContext(authorityUri, false); var cert = GetCertificate(thumbprint); //this is the utility method called out above ClientAssertionCertificate cac = new ClientAssertionCertificate(clientId, cert); var authResult = ac.AcquireTokenAsync(resourceUri, cac).Result; #next section makes calls to SharePoint Online but could easily be to another resource using (ClientContext cc = new ClientContext(url)) { cc.ExecutingWebRequest += (s, e) => { e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authResult.AccessToken; }; #make calls through the client context object #… }
Conclusion
This process is part of a much larger solution used to make authenticated calls to an Azure AD application from an Azure Function. I am working on publishing that solution as a sample for others to reference. I am hopeful that I’ll have something available within a month. For the time being feel free to reference the above steps and code snippets for use in your own project. Feel free to contact me or leave a comment if you have questions or feedback.
-Frog Out
This blog post is already a bit older one but it still helped me out a lot, so thanks for that Brian!
Something that I had to add in my configuration of the Azure Function was the “WEBSITE_LOAD_CERTIFICATES” application setting, otherwise it wouldn’t load the certificate.
LikeLike
Thank for you for the feedback. Yes, that application setting can be necessary in order to read the certificate from the store. Thanks for the callout. You can read more about certificate usage here: https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate-in-code
LikeLike