Decode JWT access and id tokens via PowerShell

JSON web tokens or JWTs are commonly used in modern websites and apps and Azure AD/Office 365 is no exception in this regard. Both the OAuth 2.0 and the OIDC protocols used by Azure AD issue some type of a JWT token as part of the authentication and authorization processes. Thus, knowing what a JWT token is and what’s contained inside it can help you with troubleshooting access issues.

There’s a lot of information about JWT tokens available online, including web-based decoder tools such as JWT.ms and JWT.io. Then again, with PowerShell we have the full strength of .NET at your fingertips, so why not simply do the decoding in the console?

So, here’s a simple function that will decode JWT Access or ID tokens issued by Azure AD. You can get the token via one of the methods exposed in the ADAL libraries, from the TokenCache on an already connected PowerShell session, via web request, by copying it from the browser URL and so on. Simply provide it as a parameter for the function.

function Parse-JWTtoken {

    [cmdletbinding()]
    param([Parameter(Mandatory=$true)][string]$token)

    #Validate as per https://tools.ietf.org/html/rfc7519
    #Access and ID tokens are fine, Refresh tokens will not work
    if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token" -ErrorAction Stop }

    #Header
    $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/')
    #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
    while ($tokenheader.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" }
    Write-Verbose "Base64 encoded (padded) header:"
    Write-Verbose $tokenheader
    #Convert from Base64 encoded string to PSObject all at once
    Write-Verbose "Decoded header:"
    [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | fl | Out-Default

    #Payload
    $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/')
    #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
    while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
    Write-Verbose "Base64 encoded (padded) payoad:"
    Write-Verbose $tokenPayload
    #Convert to Byte array
    $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
    #Convert to string array
    $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
    Write-Verbose "Decoded array in JSON format:"
    Write-Verbose $tokenArray
    #Convert from JSON to PSObject
    $tokobj = $tokenArray | ConvertFrom-Json
    Write-Verbose "Decoded Payload:"
   
    return $tokobj
}

Some basic checks are put in place, but this is in no means a complete solution that can handle all types of tokens. In addition, no attempt is made to verify the token signature.

Here’s an example of running the function:

Output of the function to Decode JWT

You can also use the –Verbose switch to expose the steps taken to decode the token:

Output of the function to decode JWT

The function will return the token payload as a PSObject, which you can then reuse as needed:

$token = Parse-JWTtoken $test

Have fun 🙂

6 thoughts on “Decode JWT access and id tokens via PowerShell

  1. Mehrdad Mirreza says:

    Hi Vasil,

    thanks for your great job, writing this function. I did some improvements. You may adapt them, if you like:

    – replaced Write-Verbose with Write-Debug. I think you need those messages only if you are debugging
    – threw away Format-List for Header
    – Compacted some code
    – added switch for also retrieving header if required
    – standardized commenting
    – renamed function to be conform with “Approved Verbs”

    regards
    Mehrdad

    function ConvertFrom-Jwt {

    [cmdletbinding()]
    param(
    [Parameter(Mandatory = $true)]
    [string]$Token,

    [Alias(‘ih’)]
    [switch]$IncludeHeader
    )

    # Validate as per https://tools.ietf.org/html/rfc7519
    # Access and ID tokens are fine, Refresh tokens will not work
    if (!$Token.Contains(“.”) -or !$Token.StartsWith(“eyJ”)) { Write-Error “Invalid token” -ErrorAction Stop }

    # Extract header and payload
    $tokenheader, $tokenPayload = $Token.Split(“.”).Replace(‘-‘, ‘+’).Replace(‘_’, ‘/’)[0..1]

    # Fix padding as needed, keep adding “=” until string length modulus 4 reaches 0
    while ($tokenheader.Length % 4) { Write-Debug “Invalid length for a Base-64 char array or string, adding =”; $tokenheader += “=” }
    while ($tokenPayload.Length % 4) { Write-Debug “Invalid length for a Base-64 char array or string, adding =”; $tokenPayload += “=” }

    Write-Debug “Base64 encoded (padded) header:`n$tokenheader”
    Write-Debug “Base64 encoded (padded) payoad:`n$tokenPayload”

    # Convert header from Base64 encoded string to PSObject all at once
    $header = [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json
    Write-Debug “Decoded header:`n$header”

    # Convert payload to string array
    $tokenArray = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($tokenPayload))
    Write-Debug “Decoded array in JSON format:`n$tokenArray”

    # Convert from JSON to PSObject
    $tokobj = $tokenArray | ConvertFrom-Json
    Write-Debug “Decoded Payload:”

    if($IncludeHeader) {$header}
    return $tokobj
    }

    Reply
  2. Pavel says:

    Thanks a lot, it was very helpful.

    But, I realized that JWT is not encoded by Base64. Actually it is encoded with Base64Url. That means token contains characters – and _ instead of + and / respectively. So at the first step you have to replace these characters or you get an error at the step of Convert.FromBase64String().

    Reply
    1. Vasil Michev says:

      Thanks Pavel. I’m by no means a real programmer, so I’m sure there are many other improvements that can be made to the code 🙂

      I’ve updated the code with some .Replace()’s

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.