-
-
Save angelo-v/e0208a18d455e2e6ea3c40ad637aac53 to your computer and use it in GitHub Desktop.
# will not work in all cases, see https://gist.github.com/angelo-v/e0208a18d455e2e6ea3c40ad637aac53#gistcomment-3439904 | |
function jwt-decode() { | |
sed 's/\./\n/g' <<< $(cut -d. -f1,2 <<< $1) | base64 --decode | jq | |
} | |
JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ | |
jwt-decode $JWT |
@lukaslihotzki, nice work :)
With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"
JWTs use base64url encoding, which neither jq
nor base64
(from GNU coreutils) can handle. So the above examples all work some of the time, not reliably.
Valid point @philpennock. Do you have a reliable solution?
Just replace the characters? jq -R 'gsub("-";"+") | gsub("_";"/") | split(".") | .[1] | @base64d | fromjson'
JWTs use base64url encoding, which neither
jq
norbase64
(from GNU coreutils) can handle. So the above examples all work some of the time, not reliably.
@philpennock is correct. Here is my solution in a shell script:
#!/bin/bash
# pad base64URL encoded to base64
paddit() {
input=$1
l=`echo -n $input | wc -c`
while [ `expr $l % 4` -ne 0 ]
do
input="${input}="
l=`echo -n $input | wc -c`
done
echo $input
}
# read and split the token and do some base64URL translation
read jwt
read h p s <<< $(echo $jwt | tr [-_] [+/] | sed 's/\./ /g')
h=`paddit $h`
p=`paddit $p`
# assuming we have jq installed
echo $h | base64 -d | jq
echo $p | base64 -d | jq
Oh, I never spoke up, sorry: I use shell around Perl because Perl had the easiest access to base64url when I last went looking, but I was impressed by the solution from @lukaslihotzki using gsub
inside jq
as being the solution with the fewest additional dependencies.
I was also trying to decode a JWT token. I could decode a JWT with:
for line in `echo $JWT | tr "." "\n"`; do echo $line | base64 --decode | jq && echo;done
@seva-ramin's script is fantastic, but just a small bug: tr [+_] [-/]
should be tr [-_] [+/]
@seva-ramin's script is fantastic, but just a small bug:
tr [+_] [-/]
should betr [-_] [+/]
Ugh! what a silly mistake! @danmactough, Thank you for catching that. I have updated my post.
@seva-ramin for me, on OSX Mojave I get base64: invalid option -- d
Changing to -D
works for last two lines:
# assuming we have jq installed
echo $h | base64 -D | jq
echo $p | base64 -D | jq
@seva-ramin for me, on OSX Mojave I get
base64: invalid option -- d
Changing to-D
works for last two lines:# assuming we have jq installed echo $h | base64 -D | jq echo $p | base64 -D | jq
Darn it. They broke base64 in Mojave? I am on Catalina and both options work. Here is the man page for base64 on Catalina.
I additionally had to remove empty parts but then it worked perfectly fine
jq -R 'split(".") | select(length > 0) | .[0],.[1] | @base64d | fromjson' <<< $1
With just jq:
jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"
@lukaslihotzki thanks, very useful 👍
With just jq:
jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"
Thanks @lukaslihotzki, very useful!
Or an universal GO approach using RawStdEncoding (with temporary file):
cat << EOFT > ./temp.go && go run ./temp.go $JWT |jq '.|select(.type=="wrapping")' ; rm ./temp.go
package main
import (
"encoding/base64"
"strings"
"fmt"
"os"
)
var encoded = os.Args[1]
func main() {
split := strings.Split(encoded, ".")
for i := 0; i < len(split); i++ {
tokenBytes, err := base64.RawStdEncoding.DecodeString(split[i])
if err != nil {
return
}
var sToken=string(tokenBytes)
fmt.Printf("%s",sToken)
}
}
EOFT
With just jq:
jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"
Excellent solution, thanks @lukaslihotzki
What about echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" | cut -d '.' -f 2 | base64 -d
what about basenc
it's part of coreutils ?
basenc -d --base64url -i <your_file> | jq
Sure; coreutils 8.31 and newer, so was not in stable OS releases at the time of the gist. Today, I'd recommend basenc.
Oh, beware though that basenc
complains about missing =
signs, even in --base64url
mode, so you'll also need to suppress stderr.
What about echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" | cut -d '.' -f 2 | base64 -d
@indian0ch thanks 👍🏻
To get around the broken/unreliable @base64d
from jq
, I got this solution:
jwtd () {
local input="${1:-}"
if [ -z "$input" ]; then
if [ ! -t 0 ]; then
input=$(cat /dev/stdin)
else
echo >&2 '✗ Need an argument or have a piped input!'
return 1
fi
fi
echo "$input" \
| jq -Rrce 'split(".")[1] | . + "=" * (. | 4 - length % 4)' \
| openssl base64 -d -A \
| jq .
}
It will append the =
padding as needed, then pipe into openssl base64 -d -A
, which I found to be more reliable and cross-platform than base64
. I tested this both on Ubuntu and MacOS.
The bash function accepts either a direct param or piped input (e.g., echo 'base64…==' | jwtd
).
@jpbochi Thanks for your script! Why don't you include the tr -- '-_' '+/'
step? openssl needs it right? (e.g. openssl/openssl#17559)
With just jq:
jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"