Authenticating to Microsoft 365 APIs with a certificate — step-by-step

Authenticating to Microsoft 365 APIs with a certificate — step-by-step
If you are looking to authenticate to Microsoft Graph or a custom API protected by Azure AD with application permissions from an Azure solution, I recommend you read my blog post about authentication with Azure Managed Identities.

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

  1. Creating the certificate
  2. Adding the certificate to an Azure AD application registration
  3. Configuring the required API permissions
  4. Using the certificate in your Azure app service
  5. Using the certificate in a Logic App or Power Automate
  6. 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.

  1. Go to https://portal.azure.com
  2. Click on Azure Active Directory on the left navigation.
  3. Click on App registrations (the new blade, not the legacy one).
  4. New registration:
    1. Give it a descriptive name.
    2. Select supported account types.
    3. 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…

  1. Go to Certificates & secrets.
  2. Click Upload certificate.
  3. Select the .cer file we generated with the PowerShell script, and click Add.
  4. 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.

  1. Go to API permissions.
  2. Click Add permission.
  3. Select the API you want to use, e.g., SharePoint or Microsoft Graph.
  4. Select Application permissions.
  5. Tick the permissions you need. Ideally these are listed in the API documentation or are self-explanatory.
  6. Click on Add permissions.
  7. 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:

  1. In the Azure portal, click on Create a resource on the left navigation
  2. Click Add and select the type of web app you want to create
  3. 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:

  1. Go to SSL settings. If you don’t see the option, you need to upgrade your app service plan to at least B1 level.
  2. Go to the Private certificates (.pfx) section
  3. Upload the .pfx certificate file. Give the same password that you used for generating the certificate.
  4. Copy the thumbprint to clipboard.
  5. Go to the Application settings section in your web app
  6. 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.

The debug config values are for local debugging purposes only. Also, never check-in any passwords to version control.

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)
As always, if you ever need to use sensitive information like this in an Azure Logic App or Power Automate, store the information in Azure Key Vault and fetch the secrets from there using the Get secret action (and enable the secure inputs and outputs in the action settings). Also, narrow down the app/flow edit permissions to an absolute minimum.

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



36 thoughts on “Authenticating to Microsoft 365 APIs with a certificate — step-by-step”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.