Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active February 21, 2024 14:47
Show Gist options
  • Save santisq/715c341fab928759edbd2c6c29d54a9f to your computer and use it in GitHub Desktop.
Save santisq/715c341fab928759edbd2c6c29d54a9f to your computer and use it in GitHub Desktop.
class JwtAssertion {
[System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate
hidden [System.Text.Encoding] $encoding = [System.Text.Encoding]::UTF8
hidden [hashtable] $claims = @{
# exp: 5-10 minutes after nbf at most
exp = [System.DateTimeOffset]::UtcNow.AddMinutes(5).ToUnixTimeSeconds()
# jti: a GUID, unique identifier for the request
jti = [guid]::NewGuid().ToString()
# nbf: (not before), Using the current time is appropriate
nbf = [System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
# iat: this is an optional claim, we dont need it here
}
hidden [hashtable] $header = @{
alg = 'RS256'
typ = 'JWT'
}
JwtAssertion([System.Security.Cryptography.X509Certificates.X509Certificate2] $cert) {
$this.Certificate = $cert
# x5t: Base64url-encoded SHA-1 thumbprint of the X.509 certificate's DER encoding.
$this.header['x5t'] = [JwtAssertion]::ToBase64UrlEncodedString($cert.GetCertHash())
}
[JwtAssertion] WithTenantId([guid] $tenantId) {
$this.claims['aud'] = 'https://login.microsoftonline.com/{0}/oauth2/v2.0/token' -f $tenantId
return $this
}
[JwtAssertion] WithClientId([guid] $clientId) {
# iss: GUID application ID
# sub: Use the same value as iss
$this.claims['iss'] = $this.claims['sub'] = $clientId.ToString()
return $this
}
[JwtAssertion] SetExpirationTime([int] $minutes) {
$this.claims['exp'] = [System.DateTimeOffset]::UtcNow.AddMinutes($minutes).ToUnixTimeSeconds()
return $this
}
static [string] ToBase64UrlEncodedString([byte[]] $bytes) {
return [System.Convert]::ToBase64String($bytes).
Replace('+', '-').Replace('/', '_').TrimEnd('=')
}
[string] GetAssertion() {
$headerAsJson = $this.header | ConvertTo-Json -Compress
$headerB64 = [JwtAssertion]::ToBase64UrlEncodedString($this.encoding.GetBytes($headerAsJson))
$claimsAsJson = $this.claims | ConvertTo-Json -Compress
$claimsB64 = [JwtAssertion]::ToBase64UrlEncodedString($this.encoding.GetBytes($claimsAsJson))
$provider = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($this.Certificate)
$signature = [JwtAssertion]::ToBase64UrlEncodedString($provider.SignData(
$this.encoding.GetBytes("$headerB64.$claimsB64"),
[Security.Cryptography.HashAlgorithmName]::SHA256,
[Security.Cryptography.RSASignaturePadding]::Pkcs1))
if ($provider) {
$provider.Dispose()
}
return "$headerB64.$claimsB64.$signature"
}
}
$cert = Get-Item Cert:\CurrentUser\My\MyThumbprint
$TenantId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$clientId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$jwtAssertion = [JwtAssertion]::new($cert).
WithTenantId($tenantId).
WithClientId($clientId).
SetExpirationTime(10). # the value is in minutes
GetAssertion()
$invokeRestMethodSplat = @{
Uri = 'https://login.microsoftonline.com/{0}/oauth2/v2.0/token' -f $tenantId
Method = 'POST'
Body = @{
client_id = $clientId
client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
client_assertion = $jwtAssertion
scope = 'https://graph.microsoft.com/.default'
grant_type = 'client_credentials'
}
}
Invoke-RestMethod @invokeRestMethodSplat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment