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,
just want to let you know that the ways shown in this article does not work anymore. I struggled with a AADSTS90002: Tenant ‘token’ not found. error. I am writing, because the blog post seemed to became the most valueable resource around this topic.
I have used the following way to get the token:
var app = ConfidentialClientApplicationBuilder.Create(_clientId)
.WithCertificate(GetCertificateFromStore(_certificateThumbprint))
.WithAuthority($”https://login.microsoftonline.com/{_tenantId}”)
.Build();
return (await app.AcquireTokenForClient(_scopes).ExecuteAsync()).AccessToken;
_scopes must only contain what you have put into the url. e.g. https://graph.microsoft.com/.default
It is important to have a .default suffix.
Thanks, Sebastian, for letting me know about the issue and that this blog post is still considered valuable even though it has been written so long ago. I’ll try to update it to reflect modern times in the near future!
Laura
Hi Laura,
Awesome Blog,
Have you had experience using the certificate for SharePoint Online, we have project where we are trying to set up Mutual TLS
CDS
Hi CDS,
Yes, I specifically wrote this blog post because I needed to authenticate with a certificate to use the SharePoint REST API.
Laura
Thank you very much for this! 🙂
I tried to implement your solution with PHP, but failed since it seems there is some issue I can not make out myself.
When sending the request to get the token I receive a 400 error ‘logon_cert’ is not a supported value for requested_token_use parameter.
I’ve posted my question on stackoverflow:
https://stackoverflow.com/questions/68199359/microsoft-graph-getting-access-token-with-certificate-results-in-400-logon-cer
One of my biggest problems is, that I do not exactly know how the request should look like. If there was an exact request definition or anything like postman request, that would also help out a lot!
I asked the question more generic here: https://stackoverflow.com/questions/68275394/microsoft-graph-how-to-get-access-token-with-certificate
Hi, I noticed your and all guides (yours is the best I´ve found btw!) similiar to it is using self signed certificate. Is it also possible to use a internal or external CA certificate?
Absolutely! And thanks. 🙂
Laura
Thanks for the quick reply! Could you please point me in the right direction on how to do so? I’ve spent hours trying to get an answer via Google but no luck.
Hi Laura
Do you have similar implementation for Java?
No, unfortunately.
Laura