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