Last active
May 23, 2024 14:25
-
-
Save dindoliboon/a4deeaafa9b126026e3139386c245f8e to your computer and use it in GitHub Desktop.
System.IdentityModel.Tokens.Jwt with PowerShell to create JWT using RS256
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
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