Maîtriser l'acquisition d'Access Token : Utiliser un certificat

Cet article fait partie d’une série. Dans celui-ci, nous mettons en avant l’utilisation d’un certificat. Consultez les autres articles sur les autres flux d’autorisation :

Bien qu’utiliser le flux Client Credentials avec un client secret soit l’une de mes méthodes préférées pour obtenir un access token, l’utilisation d’un certificat est l’une des plus sécurisées. C’est identique à l’utilisation d’un client secret, sauf que vous spécifiez client_assertion et client_assertion_type dans le body de la requête au lieu de client_secret.

Tandis que le paramètre client_assertion_type a toujours la même valeur, cette forme d’authentification repose sur le paramètre client_assertion. Il s’agit d’un JWT (JSON Web Token) composé des trois parties suivantes, séparées par un point :

  • Un header encodé en base64
  • Un payload encodé en base64
  • Une signature encodée en base64

La sécurité de cette forme d’authentification réside dans le fait que vous pouvez indiquer dans le payload pendant combien de temps et jusqu’à quand exactement le JWT peut rester valide. Et le fait que la signature est signée numériquement avec la clé privée de votre certificat. Ainsi, l’inscription d’application peut vérifier l’authenticité avant d’accorder un access token, puisque ce même certificat est configuré dans l’inscription d’application Entra.

Pour obtenir un access token avec ce flux d’autorisation, nous devons faire 3 choses :

  • Créer un certificat et le télécharger dans notre inscription d’application Entra
  • Créer un JWT
  • Utiliser ce JWT pour obtenir un access token

Créer le certificat

Commençons par créer le certificat. Nous pouvons le faire en utilisant PowerShell. Ce certificat est ajouté à l’emplacement des certificats de l’ordinateur. Ensuite, nous l’exporterons afin de pouvoir le télécharger dans notre inscription d’application Entra. Avec ce morceau de code PowerShell, nous pouvons créer et exporter le certificat. Notez que j’utilise l’emplacement C: pour exporter mon certificat, mais vous pouvez choisir un emplacement de votre choix.

$certname = "MySyperAwesomeCertificate"
$cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256
Export-Certificate -Cert $cert -FilePath "C:\$certname.cer"

Si tout se passe bien, vous devriez voir quelque chose comme ceci :

Maintenant que nous avons notre certificat, nous pouvons le télécharger dans notre inscription d’application Entra dans la section Certificates & secrets :

  • Cliquez sur Certificates & secrets
  • Cliquez sur la section Certificates
  • Cliquez sur Upload certificate

Ensuite, un panneau s’ouvre sur la droite. Sélectionnez votre certificat depuis l’emplacement où vous l’avez exporté et donnez une description à votre certificat (facultatif).

Créer le JWT

Pour obtenir un JWT basé sur votre certificat, j’utilise le script PowerShell ci-dessous. Dans ce script, vous devez fournir 3 paramètres :

  • Votre tenant ID
  • Votre client ID
  • Le thumbprint de votre certificat. Si vous ne savez pas comment obtenir le thumbprint, vous pouvez ouvrir votre certificat depuis l’emplacement où vous l’avez exporté et cliquer sur les détails. En bas des détails, vous trouverez le thumbprint.

Deux sections que je souhaite mettre en évidence sont le header et le payload. Ce sont 2 objets qui sont finalement encodés en une chaîne base64. Le header est composé des paramètres suivants :

  • alg : (algorithme) Doit être RS256
  • typ : (type) Doit être JWT
  • x5t : (X.509 Certificate SHA-1 Thumbprint) utilisé pour indiquer le thumbprint SHA-1 d’un certificat X.509 utilisé pour signer le jeton JWT.

Le payload est composé des paramètres suivants :

  • aud : (audience) identifie les destinataires auxquels le JWT est destiné. Dans ce cas, il s’agit de https://login.microsoft.com/[YOUR_TENANT_ID]/oauth2/v2.0/token.

  • exp : (expiration time) indique quand le JWT doit expirer. Cela garantit que le JWT n’est pas valide indéfiniment, mais seulement pour une certaine période. Le code ci-dessous utilise 5 minutes.

  • iss : (issuer) Indique qui a émis le JWT, c’est-à-dire l’entité responsable de la création et de la signature du token. Dans ce cas, le client ID.

  • jwt : (JWT ID) Un identifiant unique (GUID) pour le token.

  • nbf : (Not Before) Le moment à partir duquel le token est valide.

  • sub : (subject) Le sujet du token, par exemple l’entité à laquelle le token s’applique. Doit être identique à iss, donc votre client ID.

  • iat : (issued at) Le moment où le token a été émis.

$TenantId = "[YOUR_TENANT_ID]"
$ClientId = "[YOUR_CLIENT_ID]"

# Load the self-signed and uploaded certificate using its thumbprint
$Certificate = Get-Item Cert:\CurrentUser\My\[YOUR_THUMBPRINT]

# Convert the certificate to a base64 string hash of the certificate
$CertificateAsBase64 = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# Create a start date for creating a timespan
$StartDate = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()

#Make a timespan from StartDate and let it expire after 5 minutes
$ExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(5)).TotalSeconds
$Expiration = [math]::Round($ExpirationTimeSpan, 0)

# Create a timespan before which the JWT MUST NOT be accepted for processing
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$Nbf = [math]::Round($NotBeforeExpirationTimeSpan, 0)

# Create the header
$Header = @{
    alg = "RS256"
    typ = "JWT"
    x5t = $CertificateAsBase64 -replace '\+', '-' -replace '/', '_' -replace '='
}

# Create the payload
$Payload = @{
    aud = "https://login.microsoftonline.com/$TenantId/oauth2/token"
    exp = $Expiration
    iss = $ClientId
    jti = [guid]::NewGuid()
    nbf = $Nbf
    sub = $ClientId
}

# Convert header to base64
$HeaderAsBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($Header | ConvertTo-Json)))

# Convert header to base64
$PayloadAsBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($Payload | ConvertTo-Json)))

# Combine header and payload with "." to form an unsigned JWT
$UnsignedJWT = $HeaderAsBase64 + "." + $PayloadAsBase64

# Retrieve the private key object from the certificate
$PrivateKey = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate))

# Create a RSA Signature padding
$RSASignaturePadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1

# Create a hashalgoritm
$HashAlgorithmName = [Security.Cryptography.HashAlgorithmName]::SHA256

# Create the JWT signature
$JWTSignature = [Convert]::ToBase64String($PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($UnsignedJWT), $HashAlgorithmName, $RSASignaturePadding)) -replace '\+', '-' -replace '/', '_' -replace '='

# Combine the signature with the JWT using "."
$SignedJWT = $UnsignedJWT + "." + $JWTSignature

Obtenir l’access token

Pour récupérer l’access token, nous devons effectuer une requête POST vers l’endpoint https://login.microsoft.com/[YOUR_TENANT_ID]/oauth2/v2.0/token avec les paramètres suivants dans le body de la requête :

  • client_id : L’identifiant de votre client, trouvable dans l’écran d’accueil de votre inscription d’application.
  • client_assertion : Le JWT que nous avons assemblé avec le script PowerShell ci-dessus ($SignedJWT)
  • client_assertion_type : urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • grant_type : client_credentials
  • scope : Le scope doit être l’identifiant de ressource de l’application contre laquelle vous souhaitez utiliser votre access token, suivi de .default. Pour Microsoft Graph, ce serait par exemple https://graph.microsoft.com/.default.

Si tout se passe bien, vous devriez obtenir une réponse comme celle-ci :