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
Thank you for this blog post, helped me a lot !
Just a quick note to say that X509Store and X509Certificate2 instances should be disposed 🙂
Hi Teddy!
You are most welcome, and thanks!
Laura
Thanks Laura for a great article. My use case is around Power Automate and making http request call for SPO rest api.
I followed the step of certificate generation, app registration, api permission, uploading .cert file and generating 64bitencoded pfx string.
However, when I make a http rest api call for SharePoint online with Azure AD app and certificate for authentication, I get following error.
{“error_description”:”Invalid issuer or signature.”}
Interestingly though. If I user graph api endpoint using the same Azure AD app and authentication method it works.
In my scenario I want to accomplish something which is not support by the graph api at the moment. e.g. joining the hub or folder copy etc.
Any insights?
Thanks,
Himanshu
Hi Himanshu,
Without seeing the complete request and ensuring all configurations are correct, it is hard to say what could be going wrong.
Laura
Hi Laura,
Thanks for posting such a nice article.
Is it possible for you to post similar article for running GRAPH API using Postman using certificate?
Thanks & Regards,
Umesh
Hi Umesh,
Sure thing, although it has to wait until I happen to need to do that at work next time. Writing blog posts at the same time when working on something makes it so much more time-efficient!
Laura
Thank you for the article. I love that all the steps are mentioned in detail and it has helped me a lot in my learning of flow and using certificates.
I am having some issues regarding using the certificate in Flow and would like your input.
This is my first time working with certificates so please forgive me if some of the questions below seem to be trivial.
I am using the Certificate I created to call the graph API using the HTTP step in Flow.
For creating the certificate step for graph api, should my DNS be graph.microsoft.com?
In the section Select supported account types. Either one of the following options will do:
Is Account in this Organization Directory Only not an option for certificates?
Setting up your azure app to use the certificate.
I am trying to connect using flow. Do I need to perform the step to Azure App Service and add the thumbprint in the azure app service?
Once again. Thank you for the great article.
Regards,
Manish
Hi Manish,
1. The DNS name can be pretty much whatever you want. It should primarily tell you what the certificate is used for.
2. You can also limit the usage to “this organization only”.
3. No, you don’t need to set up the Azure app service etc. if you just want to use Power Automate (prev. Microsoft Flow). For that, you just need to get the base64 string of the pfx certificate.
Laura
Hi Laura,
I had previously been loading the certificate differently: by loading it directly from KeyVault. Intermittently I got the error “Keyset does not exist”. By using your method it loads reliably. I guess it’s important to have the certificate installed on the local machine/key store rather than loading purely in memory.
Thanks!
Johnny
Hi Jonathan,
Glad that you got it working reliably using this method. 🙂
Laura