# Debug a JWT token expiration locally with bash or PowerShell

**Date:** 2024-12-09  
**Author:** Kees C. Bakker  
**Categories:** Automation  
**Tags:** JWT  
**Original:** https://keestalkstech.com/debug-a-jwt-token-expiration-locally-with-bash-or-powershell/

![Debug a JWT token expiration locally with bash or PowerShell](https://keestalkstech.com/wp-content/uploads/2024/12/austin-chan-ukzHlkoz1IE-unsplash.jpg)

---

Whenever you need to check the meta data of a JWT token, you might be tempted to use [jwt.io](https://jwt.io/) and paste your token in. Of course you should not do this, but how else would you debug your token? Well, a small Google will give you a ton of scripts that can do this. I've taken some of these scripts and added some nice token expiration debugging to it.

## JQ + Bash

Usually I have [JQ](https://jqlang.github.io/jq/) installed to do some JSON debugging. This script will take your JWT as input and process it locally:

```
#!/bin/bash
set -e

# Decoding a JWT token, based on the discussion here:
# https://gist.github.com/thomasdarimont/46358bc8167fce059d83a1ebdb92b0e7
# More here:
# https://keestalkstech.com/2024/12/debug-a-jwt-token-expiration-locally-with-bash-or-powershell/

function jwt_decode(){
    jq -R 'split(".") | .[1] | @base64d | fromjson' 
```

Let's execute it on Windows using WSL2 with a test token:

```txt
bash jwt.sh eddyJhbGciOiJSUzI1NiIsInRasfasff5cCI6IkpXVCJ9.ewogICJpc3MiOiAidGVzdC1zZXJ2aWNlIiwKICAiYXVkIjogIm91ci1zZXJ2aWNlIiwKICAidXNlcm5hbWUiOiAidHN0dXNyIiwKICAiaWF0IjogMTczMzcxNTI4MiwKICAiZXhwIjogMjUyMjExNTI4Mgp9.ew-4BWfh815a_pppqlDqrjvj5CY1ZS2uYdiVOeazCDutlfnxdmrqwgSSo4Ot2riT8nMOozoN-68AioEhFphem6fFTLbqDCjONIi_ncxkSUaedavzFwvJBW0HugTfYpoUTq0uR344ffffftEaPCPg6n3GrPKFTEitzoYuVJdMWjHa_IHVzDygpMmErjBr43qPDU9TGQaYtcWFvtDOgNvGwdUl5KTauW716HBvbvBgO4zLqH_B9c80StOSt8L2aJiX6Ue5ZMgmipPdddK4mK2Vb9PoikUftv1bej_prvHBOYYVAwq-de9Ewn7P-12KVRMpRy9kuymqBgJtXVbDXdddUQY1sVwhG9cR_ryA

{
  "iss": "test-service",
  "aud": "our-service",
  "username": "tstusr",
  "iat": 1733715282,
  "exp": 2522115282
}

Token expires at: Fri Dec  3 04:34:42 CET 2049
Time remaining: 13139971 minutes.
```

The token I've used is valid for 25 years ([it is a debug token](https://keestalkstech.com/2024/11/simple-jwt-access-policies-for-api-security-in-net/#bonus-a-key-generator)). I've displayed the number of minutes to expiration to make the date more readable, so you don't have to do the math in your head.

## Powershell

If you're on Windows you could use this PowerShell version to debug your JWT headers to see if your token is still active:

```
# Decoding a JWT token, based on the discussion here:
# https://gist.github.com/thomasdarimont/46358bc8167fce059d83a1ebdb92b0e7
# More here:
# https://keestalkstech.com/2024/12/debug-a-jwt-token-expiration-locally-with-bash-or-powershell/

function Decode-JWT {
    param (
        [string]$Token
    )

    $parts = $Token -split '\.'
    if ($parts.Count -ne 3) {
        Write-Error "Invalid JWT format."
        return $null
    }

    try {
        $payloadBase64 = $parts[1].PadRight($parts[1].Length + (4 - $parts[1].Length % 4) % 4, '=')
        $payload = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payloadBase64))
        return $payload | ConvertFrom-Json
    } catch {
        Write-Error "Failed to decode or parse JSON payload. Ensure the token is a valid Base64-encoded string."
        return $null
    }
}

function Check-Expiration {
    param (
        [string]$Token
    )

    $payload = Decode-JWT $Token
    if (-not $payload) {
        Write-Error "Invalid JWT or unable to decode."
        return
    }

    if (-not $payload.exp) {
        Write-Error "No expiration ('exp') field found in the token."
        return
    }

    $exp = [long]$payload.exp
    $now = [long][DateTimeOffset]::Now.ToUnixTimeSeconds()

    if ($now -lt $exp) {
        $remaining = $exp - $now
        $minutes = [math]::Floor($remaining / 60)
        Write-Host "Token expires at: $(Get-Date -Date ([datetime]::FromFileTimeUtc($exp * 10000000 + 116444736000000000)))"
        Write-Host "Time remaining: $minutes minutes."
    } else {
        Write-Host "Token has expired. Expired at: $(Get-Date -Date ([datetime]::FromFileTimeUtc($exp * 10000000 + 116444736000000000)))"
    }
}

if ($args.Count -eq 0) {
    Write-Error "No JWT provided."
    return
}

$Token = $args[0]

Decode-JWT $Token
Check-Expiration $Token
Write-Host ""
```

You can check it like this:

```txt
./jwt.ps1 eddyJhbGciOiJSUzI1NiIsInRasfasff5cCI6IkpXVCJ9.ewogICJpc3MiOiAidGVzdC1zZXJ2aWNlIiwKICAiYXVkIjogIm91ci1zZXJ2aWNlIiwKICAidXNlcm5hbWUiOiAidHN0dXNyIiwKICAiaWF0IjogMTczMzcxNTI4MiwKICAiZXhwIjogMjUyMjExNTI4Mgp9.ew-4BWfh815a_pppqlDqrjvj5CY1ZS2uYdiVOeazCDutlfnxdmrqwgSSo4Ot2riT8nMOozoN-68AioEhFphem6fFTLbqDCjONIi_ncxkSUaedavzFwvJBW0HugTfYpoUTq0uR344ffffftEaPCPg6n3GrPKFTEitzoYuVJdMWjHa_IHVzDygpMmErjBr43qPDU9TGQaYtcWFvtDOgNvGwdUl5KTauW716HBvbvBgO4zLqH_B9c80StOSt8L2aJiX6Ue5ZMgmipPdddK4mK2Vb9PoikUftv1bej_prvHBOYYVAwq-de9Ewn7P-12KVRMpRy9kuymqBgJtXVbDXdddUQY1sVwhG9cR_ryA

iss      : test-service
aud      : our-service
username : tstusr
iat      : 1733715282
exp      : 2522115282

Token expires at: 12/03/2049 03:34:42
Time remaining: 13139952 minutes.
```

## Final thoughts

So debugging JWT locally is super easy. Extending these scripts can make it easier to check if your token is expired or not.

Here is a [pure bash version](https://gist.github.com/lmammino/920ee0699af627a3492f86c607c859f6) of the Bash script that does not use JQ. I did not include it because of licensing.

The code is on GitHub, so check it out: [code gallery / 2. simple JWT access policies for API security in .NET](https://github.com/KeesCBakker/keestalkstech-code-gallery/tree/main/02.simple-jwt-access-policies-for-api-security-in-net).
