Introduction
To connect to MS365 services via GRAPH, there are several authentication scenarios. In the case of an application that has application-type permissions and needs to authenticate with a certificate, it is more complex to connect without using the Microsoft GRAPH SDK. This article explains how to request an access token with a secret or a certificate without using the Microsoft GRAPH SDK. The examples provided is in PowerShell, but it can be adapted to any programming language.
Request MS365 access token without GRAPH SDK with a secret key
You need to have:
tenantId
clientId
clientSecret
The BODY of the POST query must contain:
grant_type='client_credentials'
client_id=$clientId
client_secret=$clientSecret
scope='https://graph.microsoft.com/.default'
“Graph.microsoft/.default” is the default value for the scope but you can use other values (see Microsoft documentations).
With these 4 parameters you can request an ACCESS TOKEN.
Here a complete script:
$tenantId = 'your_tenantid'
$clientId = 'your_app_client_id'
$clientSecret = 'your_app_secret_key'
# Construct the request body for obtaining an access token
$body = @{
grant_type = 'client_credentials'
client_id = $clientId
client_secret = $clientSecret
scope = 'https://graph.microsoft.com/.default'
}
# Send the request to obtain an access token
$accessToken = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token -Body $body
# The access token is in the access_token property of the response object
$accessToken.access_token
Note: Don’t forget to use TLS1.2 in your powershell session.
Request MS365 access token without GRAPH SDK with a certificate
You need to have:
tenantId
clientId
PFX file
with your private/public keysPassword of the PFX
file
The BODY of the POST query must contain:
grant_type = 'client_credentials'
client_id = $clientid
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
client_assertion = ‘your_signed_JwtToken_with_random_jti_$signedJwtToken’
scope = 'https://graph.microsoft.com/.default
The certificate will be used to generate the “Signed JWT request token” (use in property client_assertion). Generate the thumbprint of your certificate. The thumbprint MUST be generated with BASE64 format.
$thumbprintBytes = $cert.GetCertHash()
$thumbprintBase64 = [Convert]::ToBase64String($thumbprintBytes)
After, you must create 2 JSON fields inside an HEADER:
$header = @{
typ = "JWT"
alg = "RS256"
x5t = $thumbprintBase64
}
Create a PAYLOAD with a unique random UID and time variables:
$validityPeriodInSeconds = 3600
$currentTimeInSeconds = [Math]::Floor([DateTimeOffset]::Now.ToUnixTimeSeconds())
$expiryTimeInSeconds = $currentTimeInSeconds + $validityPeriodInSeconds
$jti = [guid]::NewGuid().ToString()
$payload = @{
aud = https://login.microsoftonline.com/$tenantId/oauth2/token
iss = $clientId
sub = $clientId
iat = $currentTimeInSeconds
exp = $expiryTimeInSeconds
jti = $jti
}
Combine HEADER and PAYLOD (JSON formatted and BASE64 converted) separated by a “.”. This value is the “clear JWT request token”.
$headerJson = ConvertTo-Json $header -Compress
$payloadJson = ConvertTo-Json $payload -Compress
$unsignedJwtToken = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerJson)) + '.' + [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
Now you can generate the RSA-SHA256 signature of this “clear JWT request token”. Generate a SHA256 hash and encrypt it with RSA and the private key of your certificate.
$dataToSignBytes = [System.Text.Encoding]::UTF8.GetBytes($unsignedJwtToken)
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportParameters($privateKey.ExportParameters([System.Security.Cryptography.RSAParameters]))
$signatureBytes = $rsa.SignData($dataToSignBytes, [System.Security.Cryptography.SHA256]::Create())
$signatureBase64 = [Convert]::ToBase64String($signatureBytes)
Concatenate the “clear JWT request token” and the RSA-SHA256 signature. This value is the “Signed JWT request token”.
$signedJwtToken = $unsignedJwtToken + '.' + $signatureBase64
Insert the “Signed JWT request token” inside the client_assertion property of the body.
$body.client_assertion = $signedJwtToken
With these parameters you can query an ACCESS JWT token.
Don’t forget to set the tenant_id in the URL.
Here a complete sample script:
# Replace the values below with your own values
$tenantId = 'your_tenantid'
$clientId = 'your_clientid'
$pfxFilePath = 'your_certificate.pfx'
$pfxPassword = 'your_certificate_password'
# Load the certificate from the PFX file and get the private key
$pfxContentBytes = [System.IO.File]::ReadAllBytes($pfxFilePath)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($pfxContentBytes, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$privateKey = $cert.PrivateKey
# Construct the request body
$body = @{
grant_type = 'client_credentials'
client_id = $clientId
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
scope = 'https://graph.microsoft.com/.default'
}
# Create the payload and header
$thumbprintBytes = $cert.GetCertHash()
$thumbprintBase64 = [Convert]::ToBase64String($thumbprintBytes)
$header = @{
typ = "JWT"
alg = "RS256"
x5t = $thumbprintBase64
}
$validityPeriodInSeconds = 3600
$currentTimeInSeconds = [Math]::Floor([DateTimeOffset]::Now.ToUnixTimeSeconds())
$expiryTimeInSeconds = $currentTimeInSeconds + $validityPeriodInSeconds
$jti = [guid]::NewGuid().ToString()
$payload = @{
aud = https://login.microsoftonline.com/$tenantId/oauth2/token
iss = $clientId
sub = $clientId
iat = $currentTimeInSeconds
exp = $expiryTimeInSeconds
jti = $jti
}
# Combine Header and Payload
$headerJson = ConvertTo-Json $header -Compress
$payloadJson = ConvertTo-Json $payload -Compress
$unsignedJwtToken = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerJson)) + '.' + [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
# Generate signature
$dataToSignBytes = [System.Text.Encoding]::UTF8.GetBytes($unsignedJwtToken)
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportParameters($privateKey.ExportParameters([System.Security.Cryptography.RSAParameters]))
$signatureBytes = $rsa.SignData($dataToSignBytes, [System.Security.Cryptography.SHA256]::Create())
$signatureBase64 = [Convert]::ToBase64String($signatureBytes)
# Generate signed JWT request token
$signedJwtToken = $unsignedJwtToken + '.' + $signatureBase64
# Add signed JWT request token to the body
$body.client_assertion = $signedJwtToken
# Send the request with $tenantid to obtain an access token
$accessToken = Invoke-RestMethod -Method Post -Uri https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token -Body $body -ContentType "application/x-www-form-urlencoded"
# The access token is in the access_token property of the response object
$accessToken.access_token
Note: Don’t forget to use TLS1.2 in your powershell session.
Lionel TRAVERSE
Microsoft 365 Certified Administrator Expert
Microsoft Certified Trainer
lionel.traverse@admin365.fr