JWT (JSON Web Tokens) implementation in PowerShell per RFC7519
Many modern APIs require crytographically signed JWT tokens. This module is to enable creating and verifying those from PowerShell and the underlying .NET Framework, without using external libraries.
The script module is published on PowerShell Gallery. Install with
Install-Module JWT
Alternatively, download the source and import the script module directly:
Import-Module /Path/to/JWT.psm1
The module provides two main functions: New-Jwt
, Test-Jwt
(also aliased to Verify-JwtSignature
), as well as service functions - ConvertFrom-Base64UrlString
, ConvertFrom-Base64UrlString
, Get-JwtHeader
, and Get-JwtPayload
. Descriptions and help for each are available by running Get-Help
.
New-Jwt
creates a JWT given a JSON payload containing a set of claims and a signing key, and Test-Jwt
verifies the JWT using public key corresponding to the signing key. In this implementation, both keys are passed to the cmdlets as -Cert
parameter of type System.Security.Cryptography.X509Certificates.X509Certificate2.
There are several ways to create an instance of X509Certificate2
. You can import PKCS #12/PFX file:
$Cert = Get-PfxCertificate /ps/jwt/jwt.pfx
Alternatively, you can instantiate this way, specifying the file and password:
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("/ps/jwt/jwt.pfx","jwt")
Yet another option is using Cert: drive, which can contain certificates with or without private keys, and is available in Windows by default:
$Cert = (Get-ChildItem Cert:\CurrentUser\My)[1]
Once you have loaded the signing key as the private key in an instance of X509Certificate2
, you can sign JWT using New-Jwt
:
PS /ps/jwt> $jwt = New-Jwt -Cert $Cert -PayloadJson '{"token1":"value1","token2":"value2"}' -Verbose
VERBOSE: Payload to sign: {"token1":"value1","token2":"value2"}
VERBOSE: Signing certificate: CN=jwt_signing_test
The private signing keys can be provided in formats other than the PKCS #12/PFX. For example, Google Cloud Platform can issue service account credentials in both PKCS #12/PFX format and Google Credentials JSON format. OpenSSL
can be used to convert between different key material formats and encodings. An example of conversion from Google Credentials format to the standard PFX can be found here
Test-Jwt
verifies a JWT provided certificate of the signing key:
$certx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("c:\ps\jwt\jwt.cer")
Test-Jwt -Jwt $jwt -Cert $certx
Note that the RSA signature verification requires only public key, which can be supplied as Base64-encoded PEM file, PFX package that doesn't contain private keys, or oher formats. Do not ever distribute your private keys!
If "alg" in the header is set to "HS256", the JWT is MACed using the HMAC SHA-256. The JWT signature is created and verified using a shared secret. To process HS256, both New-Jwt
and Test-Jwt
require -Secret
parameter, which can be a byte array (to avoid any ambiguity), or a string. The string will be converted to bytes using UTF-8 decoder:
PS /ps/jwt> New-Jwt -Header '{"typ":"JWT", "alg":"HS256"}' -PayloadJson '{"a": "b"}' -Secret 'I❤️#@(₴?$0'
eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ.eyJhIjogImIifQ.iFV0DXLqK_84NyEVqBClSIVRvWufv-9v0RIi9p10cdM
PS /ps/jwt> 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ.eyJhIjogImIifQ.iFV0DXLqK_84NyEVqBClSIVRvWufv-9v0RIi9p10cdM' | Test-Jwt -Secret 'I❤️#)(₴?$0'
False
An unsecured JWT has "alg" header parameter set to "none", and no signature part. It must be supported for RFC7519 compliance. Just header and payload are required to create unsecured JWT:
New-Jwt -Header '{"typ":"JWT", "alg":"none"}' -PayloadJson '{"a": "b"}'
Test-Jwt
just verifies that the signature part is not present, as required by the RFC:
PS /ps/jwt> 'eyJ0eXAiOiJKV1QiLCAiYWxnIjoibm9uZSJ9.eyJhIjogImIifQ.' | Test-Jwt
True
Test-Jwt
will return $false if the signature part is non-empty string. Be aware that the "none" attack is a way of testing security of systems utilising JWT - refer to "A Look at The Draft for JWT Best Current Practices" for details.
Get-JwtHeader
decodes the JWT header:
PS /ps/jwt> $jwt | Get-JwtHeader | ConvertFrom-Json -AsHashtable
Name Value
---- -----
alg RS256
typ JWT
Get-JwtPayload
decodes the payload part of the input JWT (usually JSON):
PS /ps/jwt> $jwt | Get-JwtPayload | ConvertFrom-JSON
token1 token2
------ ------
value1 value2
ConvertFrom-Base64UrlString
is RFC4648 base64url decoder. Outputs string, or a byte array is -AsByteArray
specified.
PS /ps/jwt> 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' | ConvertFrom-Base64UrlString
{"alg":"RS256","typ":"JWT"}
ConvertTo-Base64UrlString
is the reverse - it's base64url encoder. Takes string or byte array input and outputs the base64url string.
PS /ps/jwt> '{"alg":"RS256","typ":"JWT"}' | ConvertTo-Base64UrlString
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
Version 1.9 was tested with Windows PowerShell 5.1 on Windows 10, as well as PowerShell 7 on Windows, Lunux (Ubuntu), and MacOS X.
In Windows, CSP is the legacy mechanism for providing cryptographic services. Not all CSPs provide the necessary functions - some don't support signing with SHA-256. To list available CSPs and their capabilities, run certutil.exe -csplist -v
and check for entry for SHA-256. One CSP that supports it is Microsoft Enhanced RSA and AES Cryptographic Provider. To find the CSP used for a particular key, run
$cert.PrivateKey.CspKeyContainerInfo.ProviderName
- Version 1.9 - adds support for HS256 and NONE for RFC7519 compliance; additional service functions and error handling.
- Version 1.1.0 - modifies the code to be compatible with RSA as well as CNG in Windows and OpenSSL provider on Linux/Macintosh.
- Version 1.0.0 - initial release.
🐞 If the code doesn't perform as expected, raise a GitHub issue. Specify the expected behaviour and the actual output/error message. Make sure you're using the latest published version of the module.
🛠️ Pull requests are welcome if you want to add functionality.