Last active
November 13, 2023 09:37
-
-
Save MVKozlov/5106ffd58a8e1e7bb91e21a0eac8f652 to your computer and use it in GitHub Desktop.
New-JwtToken implementation in powershell
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
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