How to authenticate to Entra ID protected APIs with a certificate—step-by-step
This blog post is meant to serve as one place to get all the info you need for setting up certificate authentication. This scenario is important when you, for example, want to use the SharePoint Online REST API with application permissions because it does not support authentication with a client secret. Microsoft Graph API does support it, but using a certificate is still recommended for production scenarios as it is more secure. You can also use these exact same steps to set up authentication for a custom API that is protected by Azure AD.
I wanted to write this article because whenever I had to set up certificate authentication, there wasn’t just one article I could look at to find all the steps that I needed to take. I want this article to be such a place, so do let me know in the comments if you notice a step missing or if you think something should be elaborated on more to make following the steps easier. Hopefully, this is also a guide you can give to your tenant admin if you don’t have the required permissions to perform all the steps by yourself.
Table of contents
- Creating the certificate
- Adding the certificate to an Azure AD application registration
- Configuring the required API permissions
- Using the certificate in your Azure app service
- Using the certificate in a Logic App or Power Automate
- Afterword
Creating the certificate
The first thing we need to do is to generate the required certificate files (.cer and .pfx). You can do that by running the script below. Make sure you run the shell as an administrator. Before running the script, adjust the configuration values to match your scenario. Save the certificate password somewhere safe — you’ll need it later.
# Run this script as an administrator # --- config start $dnsName = "mytenant.sharepoint.com" # Your DNS name $password = "Come up with something secure!" # Certificate password $folderPath = "C:\temp" # Where do you want the files to get saved to? The folder needs to exist. $fileName = "mycert" # What do you want to call the cert files? without the file extension $yearsValid = 10 # Number of years until you need to renew the certificate # --- config end $certStoreLocation = "cert:\LocalMachine\My" $expirationDate = (Get-Date).AddYears($yearsValid) $certificate = New-SelfSignedCertificate -DnsName $dnsName -CertStoreLocation $certStoreLocation -NotAfter $expirationDate -KeyExportPolicy Exportable -KeySpec Signature $certificatePath = $certStoreLocation + '\' + $certificate.Thumbprint $filePath = $folderPath + '\' + $fileName $securePassword = ConvertTo-SecureString -String $password -Force -AsPlainText Export-Certificate -Cert $certificatePath -FilePath ($filePath + '.cer') Export-PfxCertificate -Cert $certificatePath -FilePath ($filePath + '.pfx') -Password $securePassword
If you get an error and realize that you haven’t installed the Azure AD PowerShell module yet, you can do it with this command.
Install-Module -Name AzureAD
Adding the certificate to an Azure AD application registration
There are a couple of places where we need to add the generated certificate to:
- The Azure AD application registration that we want to use for authentication.
- The application we want to authenticate from.
Let’s set up the AAD app registration first.
Creating the application registration
If you don’t yet have an existing app registration, here are the steps for doing that. If you do, you can skip this section and go to the Adding the certificate section.
- Go to https://portal.azure.com
- Click on Azure Active Directory on the left navigation.
- Click on App registrations (the new blade, not the legacy one).
- New registration:
- Give it a descriptive name.
- Select supported account types.
- Redirect URI (optional). Where do you want the user to get directed to after logging in? For daemon apps, you don’t need to specify this, but for web apps, you might want to set it to point to, e.g., the home page. Also, you most likely want to select Web from the dropdown, unless you are developing a desktop or mobile app.
After your app has been created, note down the following information from the Overview tab. You’ll need these in your solution.
- Application (client) ID
- Directory (tenant) ID
Adding the certificate
In your application registration…
- Go to Certificates & secrets.
- Click Upload certificate.
- Select the .cer file we generated with the PowerShell script, and click Add.
- Note down the thumbprint that appears in the view. You will need this later.
Configuring the required API permissions
Don’t forget to configure the permissions that are required for using the desired API.
- Go to API permissions.
- Click Add permission.
- Select the API you want to use, e.g., SharePoint or Microsoft Graph.
- Select Application permissions.
- Tick the permissions you need. Ideally these are listed in the API documentation or are self-explanatory.
- Click on Add permissions.
- And finally, you also need to click the Grant admin consent for organization button and then Yes. If you don’t have the permissions to do this, you need to ask your tenant admin to hit the button for you.
When permissions have been granted successfully, the warning triangles are replaced with green checkmarks in the view.
Using the certificate in your Azure app service
If you don’t yet have an app in Azure for your solution, creating one is very easy:
- In the Azure portal, click on Create a resource on the left navigation
- Click Add and select the type of web app you want to create
- Given the option, create the web app with at least B1 level app service plan. You can also change the plan later.
Setting up your Azure app to use the certificate
In your web app:
- Go to SSL settings. If you don’t see the option, you need to upgrade your app service plan to at least B1 level.
- Go to the Private certificates (.pfx) section
- Upload the .pfx certificate file. Give the same password that you used for generating the certificate.
- Copy the thumbprint to clipboard.
- Go to the Application settings section in your web app
- In the application settings section, add a new setting with Name WEBSITE_LOAD_CERTIFICATES and the thumbprint as the Value.
C# code for your solution
Below you can find a class called Authorization which contains methods for getting an access token with a certificate. The code fetches the certificate from the web app store using the thumbprint when it is run in Azure, but if you are debugging the code locally, it will use the certificate (.pfx) file from your local folder. For this debugging purpose, you need to specify the file path and the certificate password you used when generating the certificate, e.g., in the config file.
Create a new Authorization class in your project and copy-paste the code below to it. Change the namespace to match your project name, and install the Microsoft.IdentityModel.Clients.ActiveDirectory NuGet package to your project.
using System; using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.IdentityModel.Clients.ActiveDirectory; namespace MyProject { public class Authorization { private readonly string _tenantId; private readonly string _clientId; private readonly string _certificateThumbprint; private readonly string _debugCertificatePath; private readonly string _debugCertificatePassword; public Authorization(string tenantId, string clientId,string certificateThumbprint, string debugCertificatePath = null, string debugCertificatePassword = null) { _tenantId = tenantId; _clientId = clientId; _certificateThumbprint = certificateThumbprint; _debugCertificatePath = debugCertificatePath; _debugCertificatePassword = debugCertificatePassword; } public async Task<string> GetAccessTokenAsync(string url) { url = GetTenantUrl(url); var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{_tenantId}/oauth2/token"); // you can also use the v2.0 endpoint URL return (await authContext.AcquireTokenAsync(url, GetCertificate(_clientId, _certificateThumbprint))).AccessToken; } private static string GetTenantUrl(string url) { const string suffix = "sharepoint.com"; var index = url.IndexOf(suffix, StringComparison.OrdinalIgnoreCase); return index != -1 ? url.Substring(0, index + suffix.Length) : url; } private ClientAssertionCertificate GetCertificate(string clientId, string thumbprint) { var certificate = Debugger.IsAttached ? GetCertificateFromDirectory(_debugCertificatePath, _debugCertificatePassword) : GetCertificateFromStore(thumbprint); return new ClientAssertionCertificate(clientId, certificate); } private static X509Certificate2 GetCertificateFromDirectory(string path, string password) { return new X509Certificate2(System.IO.Path.GetFullPath(path), password, X509KeyStorageFlags.MachineKeySet); } private static X509Certificate2 GetCertificateFromStore(string thumbprint) { var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); var certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); store.Close(); return certificates[0]; } } }
The GetTenantUrl method is there just in case the URL is pointing to some specific SharePoint site collection. We only want the root site URL. The URL can be something else too than a SharePoint address (e.g., https://graph.microsoft.com), in which case nothing happens to it.
To use the methods, create a new Authorization type of object and use that to fetch an access token for the desired resource (URL). You should fetch the parameter values from, e.g., application settings.
var auth = new Authorization(tenantId, clientId, certificateThumbprint, debugCertificatePath, debugCertificatePassword); var accessToken = await auth.GetAccessTokenAsync(url);
You can then add the access token to the Authorization header when you make a REST request to the desired API. In the example below, we make a POST request with a JSON body.
var response = HttpRequest.GetResponse(url, accessToken, body);
using System.Net.Http; using System.Net.Http.Headers; using System.Text; namespace MyProject { public class HttpRequest { public static HttpResponseMessage GetResponse(string url, string accessToken, string body) { using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue($"Bearer {accessToken}"); var content = new StringContent(body, Encoding.UTF8, "application/json"); return httpClient.PostAsync(url, content).Result; } } } }
Using the certificate in a Logic App or Power Automate
Below is an example of how you can use the HTTP action to call an API and authenticate with the certificate.
In addition to the tenant ID and client ID, you also need to provide the pfx certificate as a base64 encoded string, and the certificate password. You can print the .pfx certificate as a base64 string in the console by running the PowerShell commands below.
$pfx = Get-Content "C:\your-directory-path\your-cert.pfx" -Encoding Byte [System.Convert]::ToBase64String($pfx)
Afterword
I hope you enjoyed reading this article and found it useful. As I mentioned in the beginning, I want this post to serve as a place to get all the info you need for setting up certificate authentication. Let me know if you notice that there is a step missing or if you feel like something should be elaborated on more!
If you are interested in reading more Office 365 development related articles, feel free to take a look at my other blog posts, and also follow me on Twitter to get notified of future posts.
Thank you for reading, it is much appreciated as always, and until next time!
Laura
Hi Laura,
thank you for the great and detailed post. It was very helpful. Should the URL passed to AuthenticationContext constructor on line 31 of Authorization class have the “/oauth2/token” stripped out? In my code I had to change it to: $”https://login.microsoftonline.com/{_tenantId}”
Thanks,
Ivan.
Hi Ivan,
Not based on my experience. Could it be that something else in your solution is adding those final fragments to the token endpoint URL and that’s why you don’t need to specify them yourself?
Laura
Hi I had followed the instructions and ran into problem.
I was trying with Active Directory OAuth but was getting error – Access token validation failure. Invalid audience.
Error information :-
{
“error”: {
“code”: “InvalidAuthenticationToken”,
“message”: “Access token validation failure. Invalid audience.”,
“innerError”: {
“request-id”: “6653167e-f63a-4252-b2b9-b5a95f96b945”,
“date”: “2019-12-03T21:54:23”
}
}
}
Hi Vasanth,
It is difficult to say what is going wrong without seeing your code. Ensure that you are passing in the correct token and scopes.
Laura
Can you figure it out how to use this in Postman?
Hi Laura,
There is a small tweak required (I think) to your instructions for getting the base64 string of the certificate for the Logic App.
You are asking to get the base64 string of the .cer file by navigating to the App Reg manifest, this won’t work as this is not the pfx file (and therefore won’t have the keys).
What worked for me was to get the base64 string of the pfx file by running the following powershell:
$certpfxfile = Get-Content .\sharepointapponly.pfx -Encoding Byte
[System.Convert]::ToBase64String($certpfxfile) | Out-File .\sharepointapponlypfxcertasbase64.txt
Then take the contents of this .txt file and use it in the logic app.
Hi Colin,
Thanks a lot!!! I’ve updated the post accordingly. 🙂
Laura
Hi Laura,
Thanks for this fantastic and comprehensive blog and all the very interesting topics you describe.
I tried and wanted to implement exactly what you described here – using a certificate in a Logic App. I implemented all steps (verified it works with the client secret for the app) but when I use the certificate I only get this error message:
“BadRequest. Could not load the certificate private key. Please check the authentication certificate password is correct and try again.”
I tried it with both certificate settings – “Active Directory OAuth” and “Client Certificate”. Both only resulting in BadRequest.
Do you have any input what could be the cause for the BadRequest and how to get it to work with certificates?
Thanks,
Stefan
Hi Stefan,
Sounds like there’s something wrong with the configurations; either in Azure AD or possibly a typo in the pfx certificate base64 encoded string.
Anyone else here faced this issue when following the instructions? I could make the instructions clearer if there is a step that can be misunderstood, or if there’s something missing.
Laura