Skip to content

Instantly share code, notes, and snippets.

@angelo-v
Last active October 21, 2024 13:19
Show Gist options
  • Save angelo-v/e0208a18d455e2e6ea3c40ad637aac53 to your computer and use it in GitHub Desktop.
Save angelo-v/e0208a18d455e2e6ea3c40ad637aac53 to your computer and use it in GitHub Desktop.
Decode a JWT via command line
# 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
Copy link

With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

@micklove
Copy link

micklove commented Mar 3, 2020

@lukaslihotzki, nice work :)

With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

@philpennock
Copy link

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.

@angelo-v
Copy link
Author

angelo-v commented Sep 2, 2020

Valid point @philpennock. Do you have a reliable solution?

@lukaslihotzki
Copy link

Just replace the characters? jq -R 'gsub("-";"+") | gsub("_";"/") | split(".") | .[1] | @base64d | fromjson'

@seva-ramin
Copy link

seva-ramin commented Sep 26, 2020

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.

@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

@philpennock
Copy link

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.

@eum602
Copy link

eum602 commented Oct 13, 2020

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

@danmactough
Copy link

@seva-ramin's script is fantastic, but just a small bug: tr [+_] [-/] should be tr [-_] [+/]

@seva-ramin
Copy link

@seva-ramin's script is fantastic, but just a small bug: tr [+_] [-/] should be tr [-_] [+/]

Ugh! what a silly mistake! @danmactough, Thank you for catching that. I have updated my post.

@genghisjahn
Copy link

genghisjahn commented May 7, 2021

@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
Copy link

seva-ramin commented May 7, 2021

@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.

image

@dbubenheim
Copy link

I additionally had to remove empty parts but then it worked perfectly fine

jq -R 'split(".") | select(length > 0) | .[0],.[1] | @base64d | fromjson' <<< $1

@subratamazumder
Copy link

With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

@lukaslihotzki thanks, very useful 👍

@suhlig
Copy link

suhlig commented Jul 5, 2022

With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

Thanks @lukaslihotzki, very useful!

@FlorinTP
Copy link

FlorinTP commented Jul 7, 2022

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

@gustavoromerobenitez
Copy link

With just jq: jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

Excellent solution, thanks @lukaslihotzki

@indian0ch
Copy link

What about echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" | cut -d '.' -f 2 | base64 -d

@kloverde25
Copy link

what about basenc it's part of coreutils ?

basenc -d --base64url -i <your_file> | jq

@philpennock
Copy link

Sure; coreutils 8.31 and newer, so was not in stable OS releases at the time of the gist. Today, I'd recommend basenc.

@philpennock
Copy link

Oh, beware though that basenc complains about missing = signs, even in --base64url mode, so you'll also need to suppress stderr.

@mvillafuertem
Copy link

What about echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" | cut -d '.' -f 2 | base64 -d

@indian0ch thanks 👍🏻

@jpbochi
Copy link

jpbochi commented Jul 12, 2024

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).

@rickgm
Copy link

rickgm commented Oct 2, 2024

@jpbochi Thanks for your script! Why don't you include the tr -- '-_' '+/' step? openssl needs it right? (e.g. openssl/openssl#17559)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment