Most Important – Why Should You Avoid Client Secrets?
Nowadays, it is recommended to avoid using client secrets and instead opt for certificate-based authentication. Client secrets pose a security risk as they are often stored in scripts or environment variables and can be compromised. Certificate-based authentication is more secure as it relies on asymmetric encryption, ensuring that private keys are not directly shared.
Why This Method?
Although the PowerShell module Connect-MGGraph provides an easy way to authenticate with Microsoft Graph, it requires module installation and may not work in all environments. This script performs authentication entirely without additional PowerShell modules, increasing flexibility and portability.
Prerequisites
Before proceeding, ensure you have the following:
- An Azure AD App Registration with the necessary API permissions.
- A valid X.509 certificate installed in the certificate store.
- The Tenant ID, Application (Client) ID, and the Thumbprint of the certificate.
How to create an Azure AD App-Registration will be covered in this Blog-Post.
PowerShell Script for Token Generation
The following script authenticates with Azure AD and retrieves an access token that can be used for API calls.
Define Required Variables
$tenantID = "YOUR TENANT ID HERE" $appID = "YOUR APP ID HERE" $thumbprint = "YOUR CERTIFICATE THUMBPRINT HERE" $resource = "https://graph.microsoft.com"
Token Generation Function
function get_token($TID, $AID, $thumbpr, $ress) {
$tenantID = $TID
$appId = $AID
$thumbprint = $thumbpr
$resource = $ress
$authUrl = "https://login.microsoftonline.com/$tenantId/oauth2/token"
# Retrieve certificate from store
$cert = Get-Item -Path Cert:\CurrentUser\My\$thumbprint
# Create JWT token
$currentTime = [System.DateTime]::UtcNow
$expiryTime = $currentTime.AddMinutes(60) # Token valid for 1 hour
$jwtHeader = @{
alg = "RS256"
typ = "JWT"
x5t = [System.Convert]::ToBase64String($cert.GetCertHash())
}
$jwtPayload = @{
iss = $appId
sub = $appId
aud = $authUrl
nbf = [System.Math]::Floor($currentTime.Subtract((Get-Date "1970-01-01 00:00:00").ToUniversalTime()).TotalSeconds)
exp = [System.Math]::Floor($expiryTime.Subtract((Get-Date "1970-01-01 00:00:00").ToUniversalTime()).TotalSeconds)
}
$jwtHeaderBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($jwtHeader | ConvertTo-Json)))
$jwtPayloadBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($jwtPayload | ConvertTo-Json)))
$signatureInput = "$jwtHeaderBase64.$jwtPayloadBase64"
# Sign token with private key
$rsa = $cert.PrivateKey
$signatureBytes = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($signatureInput), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$jwtSignatureBase64 = [System.Convert]::ToBase64String($signatureBytes)
$jwtToken = "$jwtHeaderBase64.$jwtPayloadBase64.$jwtSignatureBase64"
# Send token request to Azure AD
$tokenRequest = @{
client_id = $appId
resource = $resource
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
client_assertion = $jwtToken
grant_type = "client_credentials"
}
# Retrieve access token
$response = Invoke-RestMethod -Uri $authUrl -Method Post -ContentType "application/x-www-form-urlencoded" -Body $tokenRequest
# Return token
return $response.access_token
}Execute Function
$Key = get_token -TID $tenantID -AID $appID -thumbpr $thumbprint -ress $resource Write-Host $Key
Script Explanation
- Retrieve Certificate: The script loads the certificate from the certificate store using its thumbprint.
- Create JWT Token: A JSON Web Token (JWT) is generated with the required claims.
- Sign Token: The JWT is signed using the private key of the certificate.
- Request Token: The signed JWT is sent to Azure AD to obtain an access token.
- Output Token: The script returns the access token, which can be used for API authentication.
Using the Access Token
Once you have the token, you can use it for API requests, such as:
$headers = @{ Authorization = "Bearer $Key" }
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me" -Headers $headers -Method Get
$responseConclusion
Certificate-based authentication significantly enhances security by eliminating the need to store static secrets. This PowerShell script enables full authentication without requiring additional modules like Connect-MGGraph, offering greater flexibility and portability.
