Pasting a jwt token into someone else’s website, like jwt.io , feels kinda wrong. Actually it feels worse than pasting your details into someone’s website to check if your accounts have been pwned. And for the record, I trust both jwt.io and Troy Hunt .
You could easily roll your own small version of what the jwt.io website does (except validate the signature) to expose the details of those pesky, base64 url encoded JWT tokens.
The post Cut is useful
showed how you can extract parts of a token, and combining it with tee
you can show all base64 decoded parts at once. By piping output into tee
, you can steer its output into three separate program paths, and still have its output displayed to stdout.
echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c \
| tee >(cut -d. -f1 | base64 -d) \
>(cut -d. -f2 | base64 -d) \
>(cut -d. -f3) \
>/dev/null
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c {"alg":"HS256","typ":"JWT"}{"sub":"1234567890","name":"John Doe","iat":1516239022}
However, tee
runs the process substitution in parallell, so the order of the result is non-deterministic, and based on the content.
Something like this ought to preserve the order
function extract_jwt {
local input=$(cat -)
echo $input | cut -d. -f1 | base64 -d
echo $input | cut -d. -f2 | base64 -d
echo $input | cut -d. -f3
}
The signature part is also base64 url encoded, but I choose to keep it encoded, as decoding it often results in non-printable characters, so you might as well keep it encoded until you really need it.
Experiencing base64: invalid input
in the printed result? Chatgpt suggests its due to JWTs using base64 url encoding
, which checks out
, and is not the same as what base64 -d
expects. The former replaces +
with -
and /
with _
, and does not use padding =
.
Swap base64 -d
for this and its gone.
function base64url_decode {
local input=$(cat -)
input="${input//-/+}" # replace - with +
input="${input//_/\/}" # replace _ with /
case $((${#input} % 4)) in # add padding
2) input+="==" ;;
3) input+="=" ;;
esac
echo "$input" | base64 -d
}
function extract_jwt {
local input=$(cat -)
echo $input | cut -d. -f1 | base64url_decode
echo $input | cut -d. -f2 | base64url_decode
echo $input | cut -d. -f3
}
I did not extensively test this, but it does make sense, and seems to work fine for the couple of JWT tokens I tested.
echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c \
| extract_jwt
{"alg":"HS256","typ":"JWT"}{"sub":"1234567890","name":"John Doe","iat":1516239022}SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Edits:
- [2024-10-10 tor] wording