Skip to content

Request an Oauth2.0 token with secret or certificate without GRAPH SDK

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 keys
  • Password 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