Functions/Test-JwtStructure.ps1

function Test-JwtStructure {
    <#
    .SYNOPSIS
        Tests a JWT for structural validity.
    .DESCRIPTION
        Validates that a JSON Web Token is structurally valid by returing a boolean indicating if the passed JWT is valid or not.
    .PARAMETER JsonWebToken
        Contains the JWT to structurally validate.
    .PARAMETER VerifySignaturePresent
        Determines if the passed JWT has three parts (signature being the third).
    .EXAMPLE
        $jwtSansSig = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyMzQ1Njc4OTAsIm5hbWUiOiJKb2huIERvZSIsImFkbWluIjp0cnVlfQ"
        Test-JwtStructure -JsonWebToken $jwtSansSig
 
        Validates the structure of a JWT without a signature.
    .EXAMPLE
        $jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.VG6H-orYnMLknmJajHx1HW9SftqCWeqE3TQ1UArx3Mk"
        Test-JwtStructure -JsonWebToken $jwt
 
        Validates the structure of a JWT with a signature.
    .NOTES
        By default a passed JWT's header and payload should base 64 URL decoded JSON. The VerifySignaturePresent switch ensures that all three parts exist seperated by a full-stop (header, payload, signature).
    .OUTPUTS
        System.Boolean
    .LINK
        https://tools.ietf.org/html/rfc7519
        https://en.wikipedia.org/wiki/RSA_(cryptosystem)
        https://en.wikipedia.org/wiki/HMAC
    #>

    [CmdletBinding()]
    [OutputType([System.Boolean])]
    Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 0)][ValidateLength(16, 131072)][System.String]$JsonWebToken,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1)][Switch]$VerifySignaturePresent
    )
    PROCESS {
        $arrayCellCount = $JsonWebToken.Split(".") | Measure-Object | Select-Object -ExpandProperty Count

        if ($PSBoundParameters.ContainsKey("VerifySignaturePresent")) {
            if ($arrayCellCount -lt 3) {
                return $false
            }
            else {
                $jwtSignature = $JsonWebToken.Split(".")[2]

                if ($jwtSignature.Length -le 8) {
                    return $false
                }
            }
        }
        else {
            if ($arrayCellCount -lt 2) {
                return $false
            }
        }

        # Test deserialization against header:
        $jwtHeader = $JsonWebToken.Split(".")[0]

        if ($jwtHeader.Length -le 8) {
            return $false
        }

        [string]$jwtHeaderDecoded = ""
        try {
            $jwtHeaderDecoded = $jwtHeader | ConvertFrom-Base64UrlEncodedString
        }
        catch {
            return $false
        }

        $jwtHeaderDeserialized = $null
        try {
            $jwtHeaderDeserialized = $jwtHeaderDecoded | ConvertFrom-Json -ErrorAction Stop
        }
        catch {
            return $false
        }

        # Per RFC 7515 section 4.1.1, alg is the only required parameter in a JWT header:
        if ($null -eq $jwtHeaderDeserialized.alg) {
            return $false
        }

        # Test deserialization against payload:
        $jwtPayload = $JsonWebToken.Split(".")[1]

        if ($jwtPayload.Length -le 8) {
            return $false
        }

        [string]$jwtPayloadDecoded = ""
        try {
            $jwtPayloadDecoded = $jwtPayload | ConvertFrom-Base64UrlEncodedString
        }
        catch {
            return $false
        }

        try {
            $jwtPayloadDecoded | ConvertFrom-Json -ErrorAction Stop | Out-Null
        }
        catch {
            return $false
        }

        return $true
    }
}