Skip to content

Instantly share code, notes, and snippets.

@joerodgers
Last active March 15, 2026 09:30
Show Gist options
  • Select an option

  • Save joerodgers/a8d69ecf8d2b9ac6d61910984dc0e011 to your computer and use it in GitHub Desktop.

Select an option

Save joerodgers/a8d69ecf8d2b9ac6d61910984dc0e011 to your computer and use it in GitHub Desktop.
Validates the signature of an Entra access token
function ConvertFrom-Base64Url
{
param
(
[Parameter(Mandatory = $true)]
[string]
$InputString,
[Parameter(Mandatory = $false,ParameterSetName="AsString")]
[switch]
$AsString,
[Parameter(Mandatory = $false,ParameterSetName="AsByteArray")]
[switch]
$AsByteArray
)
$s = $InputString.Replace('-', '+').Replace('_', '/')
switch( $s.Length % 4 )
{
2 { $s += '==' }
3 { $s += '=' }
0 { }
default { throw "Invalid Base64Url string length." }
}
$bytes = [Convert]::FromBase64String($s)
if( $PSCmdlet.ParameterSetName -eq "AsByteArray" )
{
return $bytes
}
return [System.Text.Encoding]::UTF8.GetString($bytes)
}
function New-RSA
{
param
(
[Parameter(Mandatory = $true)]
[byte[]]
$Modulus,
[Parameter(Mandatory = $true)]
[byte[]]
$Exponent
)
$rsaParams = [System.Security.Cryptography.RSAParameters]::new()
$rsaParams.Modulus = $Modulus
$rsaParams.Exponent = $Exponent
$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportParameters($rsaParams)
return $rsa
}
function ConvertTo-Base64Url
{
param
(
[parameter(Mandatory = $true)]
[string]
$InputString
)
$bytes = [Text.Encoding]::UTF8.GetBytes($InputString)
$base64 = [Convert]::ToBase64String($bytes)
$base64 = $base64.Split('=')[0]
$base64 = $base64.Replace('+','-').Replace('/','_')
return $base64
}
function ConvertTo-Sha256HashedBase64UrlString
{
param
(
[parameter(Mandatory = $true)]
[string]
$InputString
)
$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString)
$sha256 = [System.Security.Cryptography.HashAlgorithm]::Create("sha256")
$hashedBytes = $sha256.ComputeHash($bytes)
$base64 = [Convert]::ToBase64String($hashedBytes)
$base64Url = $base64 -replace '\+', '-' -replace '/', '_' -replace '=', ''
return $base64Url
}
function Get-EntraSigningCertificiate
{
param
(
[Parameter(Mandatory = $true)]
$Kid
)
$response = Invoke-RestMethod -Method Get -Uri "https://login.microsoftonline.com/common/discovery/keys" -ErrorAction Stop
return $response.keys | Where-Object -Property kid -eq $Kid | Select-Object -First 1
}
function Test-EntraAccessTokenSignature
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)]
[string]
$AccessToken
)
begin
{
$parts = $AccessToken.Split('.')
}
process
{
if ($parts.Count -ne 3)
{
throw "Token is not a JWT (expected 3 dot-separated parts)."
}
$header = ConvertFrom-Base64Url -InputString $parts[0] -AsString | ConvertFrom-Json
$payload = ConvertFrom-Base64Url -InputString $parts[1] -AsString | ConvertFrom-Json
# sha265 hash the nonce value
$hashedNonce = ConvertTo-Sha256HashedBase64UrlString -InputString $header.nonce
$rawheader = ConvertFrom-Base64Url -InputString $parts[0] -AsString
$rawheader = $rawheader -replace $header.nonce, $hashedNonce
# update header with hashed nonce string
$parts[0] = ConvertTo-Base64Url -InputString $rawheader
$jwk = Get-EntraSigningCertificiate -Kid $header.kid
try
{
$rsa = New-RSA -Modulus (ConvertFrom-Base64Url -InputString $jwk.n -AsByteArray) -Exponent (ConvertFrom-Base64Url -InputString $jwk.e -AsByteArray)
$signedContent = [System.Text.Encoding]::ASCII.GetBytes( "$($parts[0]).$($parts[1])" )
$signatureBytes = ConvertFrom-Base64Url -InputString $parts[2] -AsByteArray
$sigValid = $rsa.VerifyData(
$signedContent,
$signatureBytes,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1
)
return $sigValid
}
finally
{
$rsa.Dispose()
}
}
}
$token = "eyJ0eXAiOiJKV1QiLCJub25jZSI6Impscmp..."
$result = Test-EntraAccessTokenSignature -AccessToken $token
$result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment