Whenever you need to check the meta data of a JWT token, you might be tempted to use 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 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' <<< "$1"
}
function check_expiration {
local payload
payload=$(jwt_decode "$1")
if [ -z "$payload" ]; then
echo "Invalid JWT or unable to decode."
return 1
fi
local exp
exp=$(echo "$payload" | jq -r '.exp')
if [ "$exp" == "null" ]; then
echo "No expiration ('exp') field found in the token."
return 1
fi
local now
now=$(date +%s)
if [ "$now" -lt "$exp" ]; then
local remaining
remaining=$((exp - now))
local minutes
minutes=$((remaining / 60))
echo "Token expires at: $(date -d @$exp)"
echo "Time remaining: $minutes minutes."
else
echo "Token has expired. Expired at: $(date -d @$exp)"
fi
}
echo ""
# Decode and display JWT info
jwt_decode "$1"
echo ""
# Check expiration
check_expiration "$1"
echo ""
Let's execute it on Windows using WSL2 with a test token:
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). 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:
./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 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.