-
-
Save kevinblumenfeld/c53fe3805de813fe88890b0f0ab8046b 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
Good to see I'm not the only one doing this. Thanks for the standalone. Saved me some time. Just wish I'd Googled for this before getting the signature part working.