Public/New-JwtToken.ps1

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 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 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")]
        [string] $Algorithm,
        [Parameter(Mandatory = $false)]
        [string] $Issuer = "",
        [Parameter(Mandatory = $false)]
        [string] $Audience = "",
        [Parameter(Mandatory = $false)]
        [int] $Expiration = 3600,
        [Parameter(Mandatory = $false)]
        $Claims,
        [Parameter(Mandatory = $true)]
        [ValidateScript( { if (($Algorithm -like "RS*") -and ($_.GetType().FullName -ne "System.Security.Cryptography.AsymmetricAlgorithm")) { throw $script:LocalizedData.NewJwtToken.Error.NotPrivateKey } else { $true } })]
        $Secret
    )

    begin
    {
        Write-Debug ($script:LocalizedData.Global.Debug.Entering -f $PSCmdlet.MyInvocation.MyCommand)
    }
    process
    {
        try
        {
            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.BuildHeader -f $Algorithm)
            $Header = @{
                alg = $Algorithm;
                typ = "JWT"
            }

            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.BuildExpiration -f $Expiration)
            $Expiry = Get-Date "$((Get-Date).AddSeconds($Expiration).ToUniversalTime())" -Uformat %s
            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.Expiry -f $Expiry)
            
            Write-Debug $script:LocalizedData.NewJwtToken.Debug.BuildPayload
            $Payload = @{
                iss = $Issuer;
                aud = $Audience;
                exp = $Expiry
            }
            
            if ($Claims)
            {
                Write-Debug ($script:LocalizedData.NewJwtToken.Debug.ClaimsType -f $Claims.GetType().Name)
                switch ($Claims.GetType().Name)
                {
                    "String"
                    {
                        try { $Claims = $Claims | ConvertFrom-Json -ErrorAction Stop }
                        catch { Write-Error -Message ($script:LocalizedData.NewJwtToken.Error.NotJson.Message -f $Claims) -Category InvalidData -CategoryActivity $MyInvocation.MyCommand -TargetType $script:LocalizedData.NewJwtToken.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 ($script:LocalizedData.NewJwtToken.Verbose.AddClaim -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 ($script:LocalizedData.NewJwtToken.Debug.HeaderToJson -f $Header)
            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.PayloadToJson -f $Payload)
            
            if ($Secret.GetType().Name -eq "SecureString")
            {
                Write-Debug $script:LocalizedData.NewJwtToken.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 ($script:LocalizedData.NewJwtToken.Debug.HeaderToBase64 -f $EncodedHeader)
            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.PayloadToBase64 -f $EncodedPayload)

            $ToBeSigned = "$EncodedHeader.$EncodedPayload"

            $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 }
            }

            switch -Regex ($Algorithm)
            {
                "HS"
                {
                    $SigningAlgorithm.Key = [System.Text.Encoding]::ASCII.GetBytes($Secret)
                    $Signature = $SigningAlgorithm.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($ToBeSigned))
                }
                "RS"
                {
                    $Signature = $Secret.SignData($ToBeSigned, $SigningAlgorithm, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
                }
            }
            
            $Signature = [Convert]::ToBase64String($Signature).Split('=')[0].Replace('+', '-').Replace('/', '_')
            Write-Debug ($script:LocalizedData.NewJwtToken.Debug.Signature -f $Signature)
            $Token = "$EncodedHeader.$EncodedPayload.$Signature"
            $Token
        }
        catch
        {
            Write-Error $_
        }
    }
    end
    {
        Write-Debug ($script:LocalizedData.Global.Debug.Leaving -f $PSCmdlet.MyInvocation.MyCommand)
    }
}