Skip to content

Instantly share code, notes, and snippets.

@dindoliboon
Last active May 23, 2024 14:25
Show Gist options
  • Save dindoliboon/a4deeaafa9b126026e3139386c245f8e to your computer and use it in GitHub Desktop.
Save dindoliboon/a4deeaafa9b126026e3139386c245f8e to your computer and use it in GitHub Desktop.
System.IdentityModel.Tokens.Jwt with PowerShell to create JWT using RS256
<#
example01-create-jwt.ps1: Create JWT with RS256 signature.
http://blog.d-apps.com/2013/08/powershell-and-json-web-token-handler.html
Tested using:
Windows 10.0.18362.628
PowerShell 7.0.0
Windows PowerShell 5.1.18362.628
NuGet 5.4.0.6315
System.IdentityModel.Tokens.Jwt 5.6.0
#>
# Download latest NuGet tool.
Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile "$env:USERPROFILE\Downloads\nuget.exe"
# Download System.IdentityModel.Tokens.Jwt 5.6.0 dependency.
& "$env:USERPROFILE\Downloads\nuget.exe" Install System.IdentityModel.Tokens.Jwt -Version 5.6.0 -OutputDirectory "$env:USERPROFILE\Downloads\.nuget"
# Import Jwt-CreateToken function.
Invoke-Expression -Command (Invoke-WebRequest -Uri 'https://gist.githubusercontent.com/dindoliboon/a4deeaafa9b126026e3139386c245f8e/raw/Jwt-CreateToken.ps1' -UseBasicParsing).Content
# Create self-signed certificate PFX.
$generateCertBlock = {
# Error: check to make sure the SignatureAlgorithm is supported
# Workaround: https://stackoverflow.com/a/52063928
$cert = New-SelfSignedCertificate -DnsName '20200318-2236@developer.contoso.com' -CertStoreLocation 'Cert:\CurrentUser\My' -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider'
$certPass = ConvertTo-SecureString -String 'notasecret' -AsPlainText -Force
Export-PfxCertificate -FilePath "$env:USERPROFILE\Downloads\test2.pfx" -Cert $cert -Password $certPass | Out-Null
Write-Output -InputObject $cert
}
if ($PSVersionTable.PSEdition -eq 'Desktop') {
$cert = Invoke-Command -ScriptBlock $generateCertBlock
Add-Type -Path "$env:USERPROFILE\.nuget\packages\newtonsoft.json\10.0.1\lib\net45\Newtonsoft.Json.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Tokens\5.6.0\lib\net45\Microsoft.IdentityModel.Tokens.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\System.IdentityModel.Tokens.Jwt\5.6.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Logging\5.6.0\lib\net45\Microsoft.IdentityModel.Logging.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.JsonWebTokens\5.6.0\lib\net45\Microsoft.IdentityModel.JsonWebTokens.dll"
} else {
# Error: Export-PfxCertificate: Cannot export non-exportable private key.
# Workaround: https://github.com/PowerShell/PowerShell/issues/12081#issuecomment-596777834
Import-Module -Name PKI -UseWindowsPowerShell
$winps = Get-PSSession -Name WinPSCompatSession
$cert = Invoke-Command -Session $winps -ScriptBlock $generateCertBlock
Add-Type -Path "$env:USERPROFILE\.nuget\packages\newtonsoft.json\10.0.1\lib\netstandard1.3\Newtonsoft.Json.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Tokens\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.Tokens.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\System.IdentityModel.Tokens.Jwt\5.6.0\lib\netstandard2.0\System.IdentityModel.Tokens.Jwt.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Logging\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.Logging.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.JsonWebTokens\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.JsonWebTokens.dll"
}
# Use certificate stored in CurrentUser\My store.
$claims = New-Object -TypeName System.Collections.Generic.List[System.Security.Claims.Claim]
$claims.Add((New-Object -TypeName System.Security.Claims.Claim('scope', 'https://www.contoso.com/api/users')))
$encToken = Jwt-CreateToken -Issuer '20200318-2236@developer.contoso.com' -Audience 'https://accounts.contoso.com/oauth2/token' -Claims $claims -Certificate $cert.Thumbprint
$decToken = (New-Object -TypeName System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler).ReadToken($encToken)
$decToken | Format-List
$decToken.Claims | Format-List
# Use certificate stored in PFX.
$pwd = ConvertTo-SecureString -String 'notasecret' -AsPlainText -Force
$encToken = Jwt-CreateToken -Issuer '20200318-2236@developer.contoso.com' -Audience 'https://accounts.contoso.com/oauth2/token' -Certificate "$env:USERPROFILE\Downloads\test2.pfx" -SigningCertificatePassword $pwd
$decToken = (New-Object -TypeName System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler).ReadToken($encToken)
$decToken | Format-List
$decToken.Claims | Format-List
<#
example02-google-admin-sdk-jwt.ps1: Accessing Google Admin SDK Directory API using JWT with RS256 signature.
You will need to create a service account on Google Cloud Platform.
See https://developers.google.com/identity/protocols/oauth2/service-account#creatinganaccount
Tested using:
Windows 10.0.18362.628
PowerShell 7.0.0
Windows PowerShell 5.1.18362.628
NuGet 5.4.0.6315
System.IdentityModel.Tokens.Jwt 5.6.0
#>
# Download latest NuGet tool.
Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile "$env:USERPROFILE\Downloads\nuget.exe"
# Download System.IdentityModel.Tokens.Jwt 5.6.0 dependency.
& "$env:USERPROFILE\Downloads\nuget.exe" Install System.IdentityModel.Tokens.Jwt -Version 5.6.0 -OutputDirectory "$env:USERPROFILE\Downloads\.nuget"
# Import Jwt-CreateToken function.
Invoke-Expression -Command (Invoke-WebRequest -Uri 'https://gist.githubusercontent.com/dindoliboon/a4deeaafa9b126026e3139386c245f8e/raw/Jwt-CreateToken.ps1' -UseBasicParsing).Content
# Import dependencies.
if ($PSVersionTable.PSEdition -eq 'Desktop') {
Add-Type -Path "$env:USERPROFILE\.nuget\packages\newtonsoft.json\10.0.1\lib\net45\Newtonsoft.Json.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Tokens\5.6.0\lib\net45\Microsoft.IdentityModel.Tokens.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\System.IdentityModel.Tokens.Jwt\5.6.0\lib\net45\System.IdentityModel.Tokens.Jwt.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Logging\5.6.0\lib\net45\Microsoft.IdentityModel.Logging.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.JsonWebTokens\5.6.0\lib\net45\Microsoft.IdentityModel.JsonWebTokens.dll"
} else {
Add-Type -Path "$env:USERPROFILE\.nuget\packages\newtonsoft.json\10.0.1\lib\netstandard1.3\Newtonsoft.Json.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Tokens\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.Tokens.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\System.IdentityModel.Tokens.Jwt\5.6.0\lib\netstandard2.0\System.IdentityModel.Tokens.Jwt.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.Logging\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.Logging.dll"
Add-Type -Path "$env:USERPROFILE\.nuget\packages\Microsoft.IdentityModel.JsonWebTokens\5.6.0\lib\netstandard2.0\Microsoft.IdentityModel.JsonWebTokens.dll"
}
# Get access token.
#
# Using OAuth 2.0 for Server to Server Applications
# https://developers.google.com/identity/protocols/oauth2/service-account
$claims = New-Object -TypeName System.Collections.Generic.List[System.Security.Claims.Claim]
$claims.Add((New-Object -TypeName System.Security.Claims.Claim('scope', 'https://www.googleapis.com/auth/admin.directory.user.readonly')))
$claims.Add((New-Object -TypeName System.Security.Claims.Claim('sub', '20200319-1453-admin@contoso.com')))
$pwd = ConvertTo-SecureString -String 'notasecret' -AsPlainText -Force
$encToken = Jwt-CreateToken -Issuer '20200319-1453-admin@contoso.iam.gserviceaccount.com' -Audience 'https://oauth2.googleapis.com/token' -Claims $claims -Certificate 'C:\Users\Kiosk\Downloads\test-ps-jwt-45605-9466f04606d4.p12' -SigningCertificatePassword $pwd
$payload = @{
grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
assertion = $encToken
}
$googleOAuthResult = Invoke-RestMethod -Method Post -Uri 'https://oauth2.googleapis.com/token' -ContentType 'application/x-www-form-urlencoded' -Body $payload
# Use our access token and retrieve a user.
#
# G Suite Admin SDK Directory API, Users: get
# https://developers.google.com/admin-sdk/directory/v1/reference/users/get
$payload = @{
Authorization = 'Bearer ' + $googleOAuthResult.access_token
}
$apiResult = Invoke-RestMethod -Method Get -Uri 'https://www.googleapis.com/admin/directory/v1/users/20200319-1453-user@contoso.com' -Headers $payload
$apiResult | Format-List
<#
System.IdentityModel.Tokens.Jwt with PowerShell to create JWT using RS256.
http://blog.d-apps.com/2013/08/powershell-and-json-web-token-handler.html
Tested using:
Windows 10.0.18362.628
PowerShell 7.0.0
Windows PowerShell 5.1.18362.628
NuGet 5.4.0.6315
System.IdentityModel.Tokens.Jwt 5.6.0
#>
function Jwt-CreateToken {
Param(
[String] $Issuer,
[String] $Audience,
[string] $Certificate,
[string] $CertificatePassword,
[System.Collections.Generic.List[System.Security.Claims.Claim]] $Claims = $null,
[SecureString] $SigningCertificatePassword,
[DateTime] $NotBefore,
[DateTime] $Expires
)
if ($null -eq $NotBefore) {
$NotBefore = Get-Date
}
if ($null -eq $Expires) {
$Expires = $NotBefore.AddHours(1)
}
$internalClaims = New-Object -TypeName System.Collections.Generic.List[System.Security.Claims.Claim]
# Merge user provided payload claims with internal payload claims.
if ($null -ne $Claims) {
$internalClaims.AddRange($Claims)
}
# Add IssuedAt claim.
$internalClaims.Add((New-Object -TypeName System.Security.Claims.Claim('iat', ([System.DateTimeOffset]$NotBefore).ToUnixTimeSeconds())))
if ((Test-Path -Path $Certificate)) {
# Use P12 or PFX certificate if it exists.
if ($PSBoundParameters.ContainsKey('SigningCertificatePassword')) {
$signingCertificateCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'notinuse', $SigningCertificatePassword
$plaintextSigningCertificatePassword = $signingCertificateCredential.GetNetworkCredential().Password
} else {
$plaintextSigningCertificatePassword = $CertificatePassword
}
$signingX509Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($Certificate, $plaintextSigningCertificatePassword, 'Export')
} else {
# Find certificate by thumbprint ID.
$signingX509Certificate = Get-ChildItem -Path "cert:\$Certificate" -Recurse | Where-Object { $_.HasPrivateKey -eq $true } | Select-Object -First 1
}
$signingCredentials = New-Object -TypeName Microsoft.IdentityModel.Tokens.X509SigningCredentials($signingX509Certificate)
$token = New-Object -TypeName System.IdentityModel.Tokens.Jwt.JwtSecurityToken($Issuer, $Audience, $internalClaims, $NotBefore, $Expires, $signingCredentials)
return (New-Object -TypeName System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler).WriteToken($token)
}
<#
PowerShell to create JWT using RS256.
http://blog.d-apps.com/2013/08/powershell-and-json-web-token-handler.html
Workaround when using System.IdentityModel.Tokens.Jwt 1.0 with certificates smaller than 2048 bits.
Exception calling "WriteToken" with "1" argument(s): "Jwt10530: The 'System.IdentityModel.Tokens.X509AsymmetricSecurityKey' for signing cannot be smaller than '2048' bits. Parameter name: key.KeySize Actual value was 1024."
In 2013, Google's API console would generate P12 keys that was 1024-bits. In 2020, the key size is now 2048-bits.
Instead of using the proof of concept code below, check out libraries listed at https://jwt.io
Tested using:
Windows 10.0.18362.628
PowerShell 7.0.0
Windows PowerShell 5.1.18362.628
#>
function Jwt-CreateToken {
Param(
[String] $Issuer,
[String] $Audience,
[string] $Certificate,
[string] $CertificatePassword,
[System.Collections.Generic.List[System.Security.Claims.Claim]] $Claims = $null,
[SecureString] $SigningCertificatePassword,
[DateTime] $NotBefore,
[DateTime] $Expires
)
function ConvertTo-Base64Url {
Param(
$InputObject
)
$inputObjectBytes = [System.Convert]::ToBase64String($InputObject)
return $inputObjectBytes.Split('=')[0].Replace('+', '-').Replace('/', '_')
}
if ($null -eq $NotBefore) {
$NotBefore = Get-Date
}
if ($null -eq $Expires) {
$Expires = $NotBefore.AddHours(1)
}
$internalHeader = @{
alg = 'RS256'
typ = 'JWT'
}
$internalClaims = @{
iss = $Issuer
aud = $Audience
exp = ([System.DateTimeOffset]$Expires).ToUnixTimeSeconds()
iat = ([System.DateTimeOffset]$NotBefore).ToUnixTimeSeconds()
nbf = ([System.DateTimeOffset]$NotBefore).ToUnixTimeSeconds()
}
# Merge user provided payload claims with internal payload claims.
if ($null -ne $Claims) {
$claims | ForEach-Object -Process {
$internalClaims.Add($_.Type, $_.Value)
}
}
$b64Header = ConvertTo-Base64Url -InputObject ([System.Text.Encoding]::UTF8.GetBytes(($internalHeader | ConvertTo-Json -Compress)))
$b64Claims = ConvertTo-Base64Url -InputObject ([System.Text.Encoding]::UTF8.GetBytes(($internalClaims | ConvertTo-Json -Compress)))
$baseSignature = $b64Header + '.' + $b64Claims
$bytesSignature = [System.Text.Encoding]::UTF8.GetBytes($baseSignature)
if ((Test-Path -Path $Certificate)) {
# Use P12 or PFX certificate if it exists.
if ($PSBoundParameters.ContainsKey('SigningCertificatePassword')) {
$signingCertificateCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'notinuse', $SigningCertificatePassword
$plaintextSigningCertificatePassword = $signingCertificateCredential.GetNetworkCredential().Password
} else {
$plaintextSigningCertificatePassword = $CertificatePassword
}
$signingX509Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2($Certificate, $plaintextSigningCertificatePassword, 'Export')
} else {
# Find certificate by thumbprint ID.
$signingX509Certificate = Get-ChildItem -Path "cert:\$Certificate" -Recurse | Where-Object { $_.HasPrivateKey -eq $true } | Select-Object -First 1
}
$bytesSignedSignature = $signingX509Certificate.PrivateKey.SignData($bytesSignature, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$signedB64Signature = ConvertTo-Base64Url -InputObject $bytesSignedSignature
return $baseSignature + '.' + $signedB64Signature
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment