Skip to content

Instantly share code, notes, and snippets.

@MVKozlov
Last active November 13, 2023 09:37
Show Gist options
  • Save MVKozlov/5106ffd58a8e1e7bb91e21a0eac8f652 to your computer and use it in GitHub Desktop.
Save MVKozlov/5106ffd58a8e1e7bb91e21a0eac8f652 to your computer and use it in GitHub Desktop.
New-JwtToken implementation in powershell
function New-JwtToken {
<#
.SYNOPSIS
Creates a JWT token.
.DESCRIPTION
Creates a JWT token.
.PARAMETER Algorithm
A string containing the algorithm to use.
.PARAMETER Issuer
A string containing the issuer to use.
.PARAMETER KeyId
A string containing the keyid to use.
.PARAMETER Audience
A string containing the audience to use.
.PARAMETER Expiration
An integer representing the number of seconds for which the token will be valid.
.PARAMETER Claims
An object, hastable or JSON string representing the claims to add or set.
If a claim was already set using a parameter, it will be overriden.
.PARAMETER Scope
Array, list of the permissions that the application requests.
.PARAMETER Secret
A string, secure string or certificate private key (for RSA) used to sign the token.
.OUTPUTS
System.String
Returns a String containing the raw token.
.EXAMPLE
New-JwtToken -Algorithm HS256 -Issuer $TokenIssuer -Audience $TokenAudience -Secret $MySecret
Description
-----------
This example will return a string conatining the token.
.EXAMPLE
New-JwtToken -Algorithm RS256 -Claims @{ iss = $TokenIssuer ; aud = $TokenAudience } -Expiration 3600 -Secret $Cert.PrivateKey
Description
-----------
This example will return a string conatining the token.
.LINK
https://tools.ietf.org/html/rfc7519
.LINK
https://jwt.io/
#>
[CmdLetBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateSet("HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512")]
[string] $Algorithm,
[Parameter(Mandatory = $true)]
[string] $Issuer = "",
[Parameter(Mandatory = $false)]
[string] $KeyId = "",
[Parameter(Mandatory = $true)]
[string] $Audience = "",
[Parameter(Mandatory = $false)]
[int] $Expiration = 3600,
[Parameter(Mandatory = $false)]
$Claims = $null,
[Parameter(Mandatory = $false)]
[string[]] $Scope = @(),
[Parameter(Mandatory = $true)]
[ValidateScript( { if (($Algorithm -notmatch "^[PR]S\d+$") -or ($_ -is [System.Security.Cryptography.AsymmetricAlgorithm])) { $true } else { throw $script:LocalizedData.NewJwtToken.Error.NotPrivateKey } })]
$Secret
)
begin
{
Write-Debug ('Entering {0}' -f $PSCmdlet.MyInvocation.MyCommand)
}
process
{
Write-Debug (' BuildHeader {0}' -f $Algorithm)
$Header = @{
alg = $Algorithm;
typ = "JWT"
kid = $KeyId
}
Write-Debug (' BuildExpiration {0}' -f $Expiration)
$iat = [DateTimeOffset]::UtcNow
$exp = $iat.AddSeconds($Expiration)
Write-Debug (' Expiry {0}' -f $Exp)
Write-Debug (' BuildPayload {0}' -f '')
$Payload = @{
iss = $Issuer
aud = $Audience
iat = $iat.ToUnixTimeSeconds()
exp = $exp.ToUnixTimeSeconds()
}
if ($Scope) {
$Payload.scope = $Scope -join ' '
}
if ($Claims)
{
Write-Debug (' ClaimsType {0}' -f $Claims.GetType().Name)
switch ($Claims.GetType().Name)
{
"String"
{
try { $Claims = $Claims | ConvertFrom-Json -ErrorAction Stop }
catch { Write-Error -Message ('Error.NotJson.Message {0}' -f $Claims) -Category InvalidData -CategoryActivity $MyInvocation.MyCommand -TargetType ' Error.NotJson.Target' -TargetName $Claims -Exception InvalidDataException }
}
default { $Claims = $Claims | ConvertTo-Json -Compress | ConvertFrom-Json }
}
$Claims | Get-Member -MemberType NoteProperty | Where-Object { $Claims."$($_.Name)" } | ForEach-Object {
$CurrentClaim = $_
Write-Verbose (' AddClaim {0} {1}' -f $CurrentClaim.Name, $Claims."$($CurrentClaim.Name)")
if ($Payload.Keys -contains $CurrentClaim.Name) {
$Payload[$CurrentClaim.Name] = $Claims."$($CurrentClaim.Name)"
}
else {
$Payload.Add($CurrentClaim.Name, $Claims."$($CurrentClaim.Name)")
}
}
}
$Header = $Header | ConvertTo-Json -Compress
$Payload = $Payload | ConvertTo-Json -Compress
Write-Debug (' HeaderToJson {0}' -f $Header)
Write-Debug (' PayloadToJson {0}' -f $Payload)
if ($Secret.GetType().Name -eq "SecureString")
{
Write-Debug ' SecretAsString'
$Credential = New-Object -TypeName pscredential("jwt", $Secret)
$Secret = $Credential.GetNetworkCredential().Password
}
$EncodedHeader = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Header)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$EncodedPayload = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Payload)).Split('=')[0].Replace('+', '-').Replace('/', '_')
Write-Debug ('HeaderToBase64 {0}'-f $EncodedHeader)
Write-Debug ('PayloadToBase64 {0}' -f $EncodedPayload)
$ToBeSigned = "$EncodedHeader.$EncodedPayload"
$toSign = [System.Text.Encoding]::UTF8.GetBytes($ToBeSigned)
$SigningAlgorithm = switch ($Algorithm)
{
"HS256" { New-Object System.Security.Cryptography.HMACSHA256 }
"HS384" { New-Object System.Security.Cryptography.HMACSHA384 }
"HS512" { New-Object System.Security.Cryptography.HMACSHA512 }
"RS256" { [Security.Cryptography.HashAlgorithmName]::SHA256 }
"RS384" { [Security.Cryptography.HashAlgorithmName]::SHA384 }
"RS512" { [Security.Cryptography.HashAlgorithmName]::SHA512 }
"PS256" { [Security.Cryptography.HashAlgorithmName]::SHA256 }
"PS384" { [Security.Cryptography.HashAlgorithmName]::SHA384 }
"PS512" { [Security.Cryptography.HashAlgorithmName]::SHA512 }
}
switch -Regex ($Algorithm)
{
"HS"
{
$SigningAlgorithm.Key = [System.Text.Encoding]::ASCII.GetBytes($Secret)
$Signature = $SigningAlgorithm.ComputeHash($toSign)
}
"RS"
{
$Signature = $Secret.SignData($toSign, $SigningAlgorithm, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
}
"PS"
{
#$rsa = [System.Security.Cryptography.RSA]::Create(4096)
#$rsa.ImportFromPem($secretKey)
$Signature = $Secret.SignData($toSign, $SigningAlgorithm, [System.Security.Cryptography.RSASignaturePadding]::Pss)
}
}
$Signature = [Convert]::ToBase64String($Signature).Split('=')[0].Replace('+', '-').Replace('/', '_')
Write-Debug (' Signature {0}'-f $Signature)
$Token = "$EncodedHeader.$EncodedPayload.$Signature"
$Token
}
end
{
Write-Debug ('Leaving {0}' -f $PSCmdlet.MyInvocation.MyCommand)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment