Certificate Based Authentication in Azure AD App Registration using POSTMAN

Introduction

Have you faced a requirement when you need to test certificate-based authentication using the POSTMAN tool. If you have, then you are at the right place. In this article, we will go through the process of registering an Enterprise AD App using PowerShell and testing the application access using a Certificate using the POSTMAN tool.

Pre-requisites

You need to have.

  • A Developer Subscription / Organization subscription
  • Application Owner rights / Global Admin rights
  • SharePoint Admin rights / Site Collection Admin access to a SharePoint Online site

Architecture

Below is the simple architecture of the application that we are going through in this article.

Azure AD Postman

  1. Postman sends a request to the Azure AD token endpoint.
  2. Postman acquires tokens from Azure AD.
  3. Postman passes the request to MS Graph with a token in the header.
  4. Postman receives a response from MS Graph.

Creating an App in Azure

Step 1. Run the PS Script for App registration. For Enterprise AD App creation, we are using the PowerShell script to do that. At first, we define the parameters and then run the Register-PnPAzureADApp command and get the certificates saved in a local folder. Below are the lines of the PS Script.

$AppRegistrationName = "CBATestApp"
$TenantName = "5wrdjv.onmicrosoft.com"
$SPOAdminUrl = "https://5wrdjv-admin.sharepoint.com"
$CertPasscode = Read-Host -AsSecureString -Prompt "Enter Passcode"
Connect-PnPOnline -Url $SPOAdminUrl -Interactive
Register-PnPAzureADApp -ApplicationName $AppRegistrationName -Tenant $TenantName -OutPath "/Users/vinaydeepayinapurapu/Desktop/VinayWorkingDocs/PowerShell/Certs/CBATest/" -CertificatePassword $CertPasscode -SharePointApplicationPermissions Sites.Selected -Interactive

Note. During this script execution, you will be prompted for the passcode, which is needed to encrypt the Self-signed certificate. Please save the passcode for extracting Public and Private keys.

Passcode

Step 2. Choose the account or enter the credentials. After entering the password, in the interactive mode, you might be asked to enter the credentials, the email ID that has an M365 developer subscription, or with organization email as mentioned in the pre-requisites. In this case, I have an M365 developer subscription and an account with Global Admin rights.

Microsoft

On successful authentication, you will be getting a message, successfully acquired Auth token.

Acquired token

Step 3. Grant Consent. It will take another 30 seconds to run the consent flow. This time, it checks the scope for which the AD app has been requested and asks for admin consent that needs Global / Tenant admin consent before provisioning.

Microsoft account

Pay attention to the requested scopes. If your account does not have Global Admin rights, you need to work with your Global Admin to grant the consent for the requested scopes. In the screenshot below, the app requests to.

  • Signin and read the user profile
  • Access to Specific Site Collections.

App info

Once you click on ‘Accept’ another window appears that says you consented to the Azure AD App.

Step 4. Validate the API permissions in Azure Portal. You can also log in to the Azure Portal (https://portal.azure.com)  Microsoft Entra  App registrations and search for newly created apps. Check the ‘Api Permissions’.

Configured permission

Step 5. Add the ‘Graph’ API permissions for scope ‘Sites.Selected’. This is needed to check the Graph API calls via POSTMAN using Certificate-Based Authentication.

Add permission

Select ‘Microsoft Graph’

Microsoft Graph

Select ‘Application Permissions’ Tab.

Application permission

Search for ‘sites’ and then select ‘Sites.Selected’.

Select site

Finally, Grant ‘Admin Consent’ for the selected ‘Scopes’.

Admin

Admin consent confirmation

Add permission in Microsoft Graph

You should be able to see a green tick after admin consent has been granted, just like the screenshot below.

Granted

Step 6. Validate the generated certs. Once the App is registered, you should see the below files in the folder where the path is mentioned in the PowerShell -OutPath parameter.

Test app

  • .cer file: Public key file imported to Azure AD Certificate section, and in turn, you will get thumb print.
  • .pfx file: private key file used to authenticate with the App and get access token with defined scopes.

Extracting the required values

Once the app is created and the certificates are available in your local folder, it is required to extract certain values.

  • Public key in pem format
  • Private key in pem format
  • Thumbprint in base 64 encoded format

Note. PEM stands for privacy-enhanced mail, which is the format used to encode the certificates. More about the PEM can be found in the references section.

Getting Private key in PEM format

For this, you will use the pfx certificate that is generated during the app creation using the PowerShell command Register-PnPAzureADApp and the ‘openssl’ command.

openssl pkcs12 -in CBATestApp.pfx  -nodes -nocerts | openssl rsa -out CBATestAppPrivate.key

Note: You will be asked for an import password. Enter the passphrase that was entered during the Azure AD App creation.

Getting Public key in PEM format

After getting the Private key, you can extract the public key using openssl by entering the below command.

openssl rsa -in CBATestAppPrivate.key -pubout -out CBATestAppPublic.pem

RSA Key

You would see the message ‘writing RSA key’. After that, you should see new public and private keys. In this case CBATestAppPublic.pem, CBATestAppPrivate.key.

Keys

Getting x5t Claim

To get the certificate thumbprint, I used the Python code. Record the certificate Thumbprint during the App creation and then supplement the Thumbprint in the below Python script to get the x5t claim.

import binascii
import base64
thumbprint_hex = 'DAF2FA27E005C4A7FAC98AC1F5837DAC02FF0579'
thumbprint_binary = binascii.unhexlify(thumbprint_hex)
x5t_compliant = base64.b64encode(thumbprint_binary).decode('utf-8')
print(x5t_compliant)

Output

Output

In this case the x5t claim is 2vL6J+AFxKf6yYrB9YN9rAL/BXk=

You can also use open SSL to get the x5t claim. Please follow the below steps

Step 1: Store all elements of chain of trust, using pkcs12 format and save as file .pem. Below is the command that needs to be run to extract the chain of trust from existing .pfx certificate.

openssl pkcs12 -in ./CBATestApp.pfx  -out CBATestApp.pem

 

It will ask for password / passphrase that is used to to generate the root certificate from CSR (Certificate Sign Request).

Step 2: Once successful enter of passphrase, it will get the certificate in .pem format. Run the below command to get the certificate fingerprint / thumprint. It will be in SHA1 binary format.

openssl x509 -in ./CBATestApp.pem -noout -fingerprint

 

Step 3: SHA1 format is not supported in any of the certificate signing standards. We have to get the base 64 encoded thumbprint. For this we need to use sed. sed is linux based package, which lets you to search encode and delete the lines that matches a pattern without using a text editor. At the backend it uses regex. After you get the encoded value from SHA1 it is required to get hexadecimal dump which is supported by POSTMAN. For that we need to use openssl xxd. XXD is a command line tool that is used to get hexadecimal dump from binary format or vice-versa. Below is the command to get the final base64 encoded thumbprint.

Echo $(openssl x509 -in ./CBATestApp.pem -fingerprint -noout) | sed ‘s/SHA1 Fingerprint=//g’ | sed ‘s/://g’ | xxd -r -ps | base64

Get Client Assertion

Go to https://jwt.io  to get the client assertion.

Header values

Substitute the x5t value that you got from the above section. Get the x5t claim.

HEADER

{

  "alg": "RS256",
  "typ": "JWT",
  "x5t":"2vL6J+AFxKf6yYrB9YN9rAL/BXk="
}

Screenshot for reference from the jwt.io site.

Header

Payload values

Unix

  • iss: this is the client ID. In this case, it is
  • jti: unique identifier. You can generate a new GUID using VS code or PowerShell. In this case, I have used the PowerShell command New-Guid.
  • nbf: stands for not before. Setting the value from unix time stamp. Copy the current timestamp value. Copy
  • sub: it is the application ID.  

The payload data after the values would be in the following JSON format.

PAYLOAD:DATA

{

  "aud": "https://login.microsoftonline.com/{TenantD}/oauth2/v2.0/token",
  "exp": 1699254916(expiration time),
  "iss": "<application client_id>",
  "jti": "<random unique identifier>",
  "nbf": 1699254916,
  "sub": "<application client_id>"

}

Screenshot for reference from jwt.io site.

Payload Data

Signature Section

In this section, there are 2 boxes. In the first box, we should be entering ‘Public Key’. You can read this value using any code editor (vs. code in this case) that was generated using OpenSSL in the above section. In this case, the file name is CBATestAppPublic.pem.  

Verify signature

CBA

In the second box, enter the RSA private key. You can read this from the VS code again. Below is the screen capture for reference. In this case file name is CBATestAppPrivate.key.

Key

Once all the values, which in this case, x5t, Public key, and Private key, are in the left-hand section, you will see the encoded client assertion value, and you should also be getting a message ‘Signature Verified’. Copy the client assertion value and save it in a notepad.

You should see the below message after entering the correct values.

Signature verified

Validating in POSTMAN

Now open the POSTMAN tool or web version and send the request. In this case, I have chosen a Graph API request.

Step 1. Set the request type as ‘POST’ and enter the below URL in the URL section. You may need to update the tenant ID according to your values.

https://login.microsoftonline.com/3b91eddf-9ad2-4024-b5e0-e13e8bf62ceb/oauth2/v2.0/token

Step 2. In the ‘body’ section, select the type x-wwwform-urlencoded. Enter the following key-value pairs.

Key Value
grant_type client_credentials
client_id <Client ID of Azure AD App>
scope https://graph.microsoft.com/.default
client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion <Generated assertion from jwt.io>

Step 3. Send the POST request as per the below screen shot. On success, you should see an access token.

Post

Access token

Step 4. Copy the access token and send it to Graph API. In this case, I am trying to get the site details. The request type is ‘Get’. In the ‘Authorization’ tab, select the type as ‘Oauth2.0’. Paste the token that you got from Step 3 under the token field. The URL I used to get site details is https://graph.microsoft.com/v1.0/sites/{hostheader}:/{SiteRelativePath}. In this case,

  • hostheader: 5wrdjv.sharepoint.com
  • SiteRelativePath: /sites/cbademo

Authorization

Token

Step 5. Validate the output. You should get the basic site details as shown screenshot below.

Validate

Step 6. Try the same access token for another site where the App permissions are not configured. You should get access denied.

App permission

This validates that the sites.selected scope in Azure AD api permissions is working as expected. We have configured the write permissions to only the CBA Demo (https://5wrdjv.sharepoint.com/sites/cbademo) SPO site.

Possible Errors


Key not found

Reason: This error could be because the x5t claim value and thumbprint are not correct or not in the correct format. Use the attached Python to get the x5t which is respected by POSTMAN.

"error_description": "AADSTS700027: The certificate with identifier used to sign the client assertion is not registered on the application. [Reason - The key was not found., Thumbprint of the key used by the client: '021B9DF69584C45FFAE4BE54ABD03D9F238C007D'. Please visit the Azure Portal, Graph Explorer, or directly use MS Graph to see configured keys for app Id '0e0f7424-58f9-4200-872b-a87952d0de5d'. Review the documentation at https://docs.microsoft.com/en-us/graph/deployments to determine the corresponding service endpoint and https://docs.microsoft.com/en-us/graph/api/application-get?view=graph-rest-1.0&tabs=http to build a query request URL, such as 'https://graph.microsoft.com/beta/applications/0e0f7424-58f9-4200-872b-a87952d0de5d']. Trace ID: 884562c9-bbab-4a4d-80e5-915eda76d902 Correlation ID: 10ecf411-12fa-433a-b28e-d7eb734a131b Timestamp: 2024-01-07 00:31:07Z",

"error_codes": [

Roles claim need to be present in the token

Reason: This could be because the scopes for the application or not correctly defined.

Fix: Login to Azure Portal and look for the application scope. In this case, I forgot the define Graph permissions for the scope ‘Sites.Selected’. Once defined and granted consent from admin the below issue is fixed.

JSON Code

Client Assertion audience claim does not match Realm Issuer

Body

This issue is due to the token URL to Azure AD not being formatted correctly. It should be in the format of https://login.microsoftonline.com/{TENANTID}/oauth2/v2.0/token.

Conclusion

Thus, in this article, we have seen how to test Graph API requests to the SPO site using Certificate-Based Authentication. We have also used Open SSL and Python scripts to get the certificate values to generate client assertion tags using https://jwt.io. Finally, we have also validated the ‘Sites.Selected’ scope for Azure AD App permissions.

References