To assist in troubleshooting, I wanted to generate JWT (JSON Web Tokens) on-the-fly with bash.

It was the easiest way (I thought) to be able to test various conditions like malformed headers, payloads, mismatching algorithms, and various other edge cases to see how my server would respond.

This nginx blog post and this superuser post were very helpful in getting my script working.

For most people, you might find that the interactive debugger available at jwt.io is actually a much better way to generate JWTs. You can click that link and live edit either the generated token on the left, or the content on the right. It's very nice and simple.

However, since I already spent all this time and energy to do it in bash, I wanted to share my results.

Edit the secret, header, and payload variables in this script as needed.

You must have jq and openssl installed for this to work. The jq syntax seemed a bit funky to me at first, so I avoided it. In retrospect, I have found it to be very powerful, and I recommend you take the opportunity to learn jq. Click the examples in the manual and play with jq online!

This will only generate JWTs with HMAC signing using SHA256. I've seen this signing referred to as both HS256 and HMACSHA256.

Thanks to Charles Duffy's input for helping to drastically improve these scripts.

#!/usr/bin/env bash

secret='SOME SECRET'

# Static header fields.
header='{
	"typ": "JWT",
	"alg": "HS256",
	"kid": "0001",
	"iss": "Bash JWT Generator"
}'

# Use jq to set the dynamic `iat` and `exp`
# fields on the header using the current time.
# `iat` is set to now, and `exp` is now + 1 second.
header=$(
	echo "${header}" | jq --arg time_str "$(date +%s)" \
	'
	($time_str | tonumber) as $time_num
	| .iat=$time_num
	| .exp=($time_num + 1)
	'
)
payload='{
	"Id": 1,
	"Name": "Hello, world!"
}'

base64_encode()
{
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | openssl enc -base64 -A
}

json() {
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | jq -c .
}

hmacsha256_sign()
{
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | openssl dgst -binary -sha256 -hmac "${secret}"
}

header_base64=$(echo "${header}" | json | base64_encode)
payload_base64=$(echo "${payload}" | json | base64_encode)

header_payload=$(echo "${header_base64}.${payload_base64}")
signature=$(echo "${header_payload}" | hmacsha256_sign | base64_encode)

echo "${header_payload}.${signature}"

Running the script should generate an encoded JWT that looks like this.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEiLCJpc3MiOiJCYXNoIEpXVCBHZW5lcmF0b3IiLCJleHAiOjE0ODE5OTQxMzgsImlhdCI6MTQ4MTk5NDEzN30.eyJJZCI6MSwiTmFtZSI6ImhleSB0aGVyZSJ9.VeREKJ8rj5UuGrKpK85-grqihFhlCkIJjte2XiFIZs8

You can use the JWT Debugger to verify the string is valid and properly signed.

You can also decode and verify the token using this script. Edit the secret variable as needed so that you can verify the JWT is properly signed.

#!/usr/bin/env bash

secret='SOME SECRET'

base64_encode()
{
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | openssl enc -base64 -A
}

base64_decode()
{
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | openssl enc -base64 -d -A
}

verify_signature()
{
	declare header_and_payload=${1}
	expected=$(echo "${header_and_payload}" | hmacsha256_encode | base64_encode)
	actual=${2}

	if [ "${expected}" = "${actual}" ]
	then
		echo "Signature is valid"
	else
		echo "Signature is NOT valid"
	fi
}

hmacsha256_encode()
{
	declare input=${1:-$(</dev/stdin)}
	printf '%s' "${input}" | openssl dgst -binary -sha256 -hmac "${secret}"
}

# Read the token from stdin
declare token=${1:-$(</dev/stdin)};

IFS='.' read -ra pieces <<< "$token"

declare header=${pieces[0]}
declare payload=${pieces[1]}
declare signature=${pieces[2]}

echo "Header"
echo "${header}" | base64_decode | jq
echo "Payload"
echo "${payload}" | base64_decode | jq

verify_signature "${header}.${payload}" "${signature}"