I still often encounter the use of user accounts to run automation, scripts, and applications to connect with Microsoft services. By user account, I mean a user object created specifically for running certain tasks. These accounts typically have complex passwords. Over time, the passwords for these service accounts are often forgotten, and none of the IT team members dare to touch them. If we change the password, we hope it doesn't break anything production-critical. It's clear that relying on a single password-protected account for a highly important service is a bad idea.
Even Microsoft is gradually pushing us to move away from these user-based service accounts and for good reasons. For example, Microsoft has announced that “starting in early 2025, MFA (Multifactor Authentication) for sign-in to Azure Command Line Interface (CLI), Azure PowerShell, and Infrastructure as Code (IaC) tools will gradually roll out to all tenants.” This means that if you have a regular user account (username and password) to interact with the Azure interfaces, you will have to use MFA. And as we all know, automations cannot deal with MFA prompts. I predict that in the future, Microsoft will not allow any interaction via user accounts to one of their APIs without MFA.
What is the right way to authenticate your script or application to your cloud tenant? Service principals are the way to go!
If you look into the Microsoft docs, you will notice that "service principals" is a broad term, and discussing all the options would be a long journey. So in this blog post, I’m going to focus on a specific scenario, although this scenario will most likely cover the majority of the use cases IT professionals will encounter.
In this blog post, I am going to demonstrate how to authenticate to the Graph API using PowerShell.
We will use an app registration to interact with the Graph API via code. When you register your application with Microsoft Entra ID, you create an identity for your application that allows it to integrate with Entra ID.
Creating an app registration is simple. Go to entra.microsoft.com > Applications > App Registrations > New App Registration. Give your app registration a fitting name and register it as a single-tenant app.
An app registration provides two methods of authentication. Each method has advantages and disadvantages, and you should uphold some general best practices.
App secrets are very easy to use. They are straightforward to create and compatible with various PowerShell modules or directly in your code. However, they come with a significant disadvantage: in reality, an app secret is just a very long string. Therefore, you need to be very careful about how you use and store this app secret in your code.
You want to avoid having the app secret in clear text in your code. There have been many cases of breaches where applications get compromised due to clear text secrets in their code. To help mitigate this risk, Microsoft has even created secret scanning for detecting clear text secrets in DevOps.
Additionally, the secret’s lifetime should not be too long. While the maximum lifetime of a secret is 2 years, Microsoft recommends setting a secret lifetime of 6 months.
It’s important to note that before April 2021, there was an option to set the Client Secret Expiration to never, which essentially gave the secret a 99-year lifetime. You can learn more about why Microsoft made this change and the additional recommendations they have for Client Secrets.
Figure 1: Creating a Client Secret in the Microsoft Entra Portal
Tip: If you are going to use secrets, make sure you give them a clear description. During the renewal of the app secret, it is often unclear what the app secret is used for. Proper documentation and naming conventions can do wonders!
When creating a secret, you will only see the secret once. Make sure to note it down in your password manager.
Figure 2: Viewing the Secret of my App Registration in the Microsoft Entra Portal.
You can use a certificate to authenticate to the Graph API by uploading it to your app registration. These certificates can be self-signed.
With the following code, you can create your own self-signed certificate.
$certName = "CN=LouSecApp"
$certDirectory = "C:\temp"
$certPassword = ConvertTo-SecureString -String "ThePasswordOfMyCertificate" -Force -AsPlainText
# Create the directory if it does not exist
New-Item -ItemType Directory -Path $certDirectory -Force
# Define file paths
$cerFilePath = Join-Path -Path $certDirectory -ChildPath "LouSec.cer"
$pfxFilePath = Join-Path -Path $certDirectory -ChildPath "LouSec.pfx"
# Create a self-signed certificate
$cert = New-SelfSignedCertificate -CertStoreLocation "Cert:\CurrentUser\My" -Subject $certName -KeyExportPolicy Exportable -KeySpec Signature -NotAfter (Get-Date).AddMonths(6)
# Export the public key to a .cer file
Export-Certificate -Cert $cert -FilePath $cerFilePath
# Export the certificate to a .pfx file
Export-PfxCertificate -Cert $cert -FilePath $pfxFilePath -Password $certPassword
Write-Output "Certificate created and exported to $certDirectory"
The script will generate two file types:
For example, if another system needs to use the same app registration and you want to reuse the same certificate, you can install it using the .pfx file.
After installing the certificate, you can verify the Thumbprint in its properties. Depending on where you installed your certificate, you can find it in CertMgr under Personal > Certificates.
Figure 3: Validating the Thumbprint of a certificate in the Certificate Properties.
Now that you have created your certificate, you can upload the .cer file in your app registration:
Figure 4: Uploading the Certificate in the Microsoft Entra Portal.
You will now find your certificate and its thumbprint saved under your app registration. We will need this thumbprint later in our code to authenticate to the Graph API.
Figure 5: Identifying the Certificate in the Microsoft Entra Portal by its Thumbprint.
Based on my experience, the Microsoft Graph API is essential for automating nearly every action in your cloud tenant. It is under active development and serves as the single point of entry for all automation tasks. It is crucial to assign the least privileged permissions to your app registration to ensure security. In this blog post, I will demonstrate how you can use an app secret or a certificate to authenticate to the Graph API.
In this example I don’t want to use any PowerShell module and just rely on pure API calls using web requests. I also want to avoid storing my secret in plain text within my code. Since I will be running this code on my personal device, I intend to save my secret as a secure string in a file for added security.
$client_secret = "<your secret here>"
$ClientSecretPath = "C:\ClientSecret.txt"
$client_secret | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File "C:\ClientSecret.txt"
This is what your ClientSecret.txt should look like:
Once you have ClientSecret.txt you can use it in the authentication flow in the script below.
# Parameters
$tenantId = "<Tenant id>
$client_id = "<client id>"
$ClientSecretPath = “C:\ClientSecret.txt”
$resource = "https://graph.microsoft.com"
$client_secret = ([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR((Get-Content $ClientSecretPath | ConvertTo-SecureString))))
# Get Access Token
$tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
$body = @{
'resource' = $resource
'client_id' = $client_id
'client_secret' = $client_secret
'grant_type' = 'client_credentials'
}
$response = Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType "application/x-www-form-urlencoded" -Body $body
$access_token = $response.access_token
# Headers
$headers = @{
"Authorization" = "Bearer $access_token"
}
# User ID or User Principal Name (UPN)
$userId = "louismastelinck@mastelinck.onmicrosoft.com"
# API Endpoint
$uri = "https://graph.microsoft.com/v1.0/users/$userId"
# Make the API call
$response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
# Output the response
$response
When analyzing the code above, you'll notice that we use the secret that was previously encrypted and stored in our file. In the "# Get Access Token" section of the script, we create a body containing all the information from our app registration. We then perform an authentication request and receive valid access tokens in return. These tokens are placed in the headers of each request we make to the Graph API.
Implementing certificate-based authentication without any modules can be quite challenging. Fortunately, we have the Microsoft Graph SDK at our disposal, which conveniently assists us in authenticating using the certificate we just created.
If the Graph SDK isn’t already installed and you wish to execute some demo code, you can proceed with its installation by following these steps:
Install-Module -Name Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser
The code below will use the thumbprint of our certificate to authenticate.
# Load the certificate from the certificate store
$CertThumbprint = "9ce6763a1412633fc842e58ff13e8b93fe5855d4" # Thumbprint of the certificate used for authentication
$Cert = Get-ChildItem Cert:CurrentUser\My\$CertThumbprint
# Define the necessary parameters
$TenantId = "f1eb716f-3738-4f1d-a41b-06cbf378a8a4" # Your Entra ID Tenant ID
$ClientId = "3ec2a898-4795-4629-9b61-27431fa6671b" # Your Application (Client) ID
# Connect to Microsoft Graph
$connection = Connect-MgGraph -ClientId $ClientId -TenantId $TenantId -Certificate $Cert
$userUPN = "louismastelinck@mastelinck.onmicrosoft.com"
# API Endpoint
$uri = "https://graph.microsoft.com/v1.0/users/$userUPN"
# Make the API call
$response = Invoke-MgGraphRequest -Method GET -Uri $uri -Headers $connection.Headers
# Output the response
$response
Once you have the certificate installed, you can easily use it in your code. The Microsoft Graph SDK does all the heavy lifting for you.
If you're new to creating automation with app registrations, these samples are an excellent starting point! We've demonstrated how to use either an app client secret or a certificate to authenticate with the Graph API. The code is flexible, so you can modify it as needed. Remember to manage your certificates and secrets carefully, keeping track of their expiration dates to avoid any issues.
The AppGov Community Forum is moderated by Microsoft Security & Identity MVPs and subject-matter experts to answer your questions around Entra ID, managing Enterprise Applications, Application Registrations, and the impact of Tenant Settings on an application's lifecycle.
For example, in regard to app registrations, CrushNetworks asked, "I have P2P Server, Sales Copilot for Microsoft Outlook, and Sales Copilot Power Virtual Agents Bot (Power Virtual Agents) all showing up affecting my score. All are Microsoft apps. Can I remove the P2P Server?” Check out the expert's response here or ask your own question.
Are you curious how your Client Secrets and Certificates are configured in your Application Registrations?
ENow has a no-cost utility, AppGov Score, that will scan your Entra ID Application Registrations and identify how many registrations have been configured with Client Secrets, as well as how many Client Secrets are about to expire, how many have already expired, and those Clients Secrets with non-standard expirations (past the 2-year lifetime recommendation). The results of this free assessment will give you a quick look at whether your application registrations are configured in line with Microsoft’s recommended practices.