Exploring The JSON Web Token (JWT)

What is a JWT?

The JSON Web Token is a safe way to transfer JSON data through a URI query string, HTTP header, or other transport means. This is a great alternative to maintaining session state without session variables and the explicit need for a database to lookup sensitive information.

The token is ‘safe’, because the JSON string object is encoded to Base64url. The base64 encoding ensures the data remains intact and isn’t modified or interpreted differently between systems. Url encoding ensures spaces are interpreted correctly and characters that have special meaning such as ‘?’ or ‘&’ are unambiguous. Two additional features add to their safety… ‘Signatures’ help ensure the data sent has not been tampered with or changed in any way. Encrypted payloads help ensure the source of the data and hides the meaning of the contents of that data.

A JWT has 3 parts,
Header
Payload (can be encrypted)
Signature (optional)

A token combines these 3 parts with the dot ‘.’ as its separator where the layout is

header.payload.signature

The header contains information about the token which should be shared to describe information such as the cryptography used to encrypt the payload.

Example:

{"typ":"JWT","alg":"HS256"}

This header describes the type of JSON object as JWT and is hashed using HMAC SHA-256. This means the token will have a signature to help prove it’s authentic and hasn’t been tampered with. A secret-key is used with the hashing algorithm to produce the signature. If a signature is not used, alg will be set to none. There are some security concerns to keep in mind with this.

Creating a JWT

Creating a JWT is easily done by using existing libraries and there are options available for many programming languages. Let’s experiment on the Linux command line to get an understanding of the basic construction.

Here are the assumptions we’ll use:

header =

{"typ":"JWT","alg":"HS256"}

payload =

{"FirstName":"Rob", "LastName":"Stanfield","Occupation":"Software Developer"}

secret_key = ABC

Base64 Encoding

Base64 encode the header

echo -n '{"typ":"JWT","alg":"HS256"}' | base64

Result:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

In some cases, the result will have one or two trailing = which is Base64 encoding padding and according to the JWT spec, should be removed. The -n option for the echo command removes the trailing newline which we don’t want to include in our token.

Next we’ll base64url encode the payload. The reason this now becomes a requirement is because of the space in “Software Developer”. The payload is also known as the claims. Here is the bash recipe to base64url encode a string that I’ll use here in this example.

echo -n '{"FirstName":"Rob", "LastName":"Stanfield","Occupation":"Software Developer"}' | base64url.sh

Result:

eyJGaXJzdE5hbWUiOiJSb2IiLCAiTGFzdE5hbWUiOiJTdGFuZmllbGQiLCJPY2N1cGF0aW9uIjoiU29mdHdhcmUgRGV2ZWxvcGVyIn0

We now have a complete minimum JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJSb2IiLCAiTGFzdE5hbWUiOiJTdGFuZmllbGQiLCJPY2N1cGF0aW9uIjoiU29mdHdhcmUgRGV2ZWxvcGVyIn0

Tamper Proof JWT With A Signature

While we have a JWT that will work, we can’t be sure it hasn’t been modified during transit. To do this, the JWT can be signed using the hash algorithm we’ve specified in the header. To sign this, we’ll take the base64 encoded token we have thus far, and hash it using the openssl library with our secret-key of “ABC”.

echo -n eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJGaXJzdE5hbWUiOiJSb2IiLCAiTGFzdE5hbWUiOiJTdGFuZmllbGQiLCJPY2N1cGF0aW9uIjoiU29mdHdhcmUgRGV2ZWxvcGVyIn0 
| openssl sha256 -hmac ABC

Result:

541eed57c382b02bfd3674ef0a4d7003c4381d641926c42284266d5c47d91dc6

We can now combine header.payload.signature to have a url safe, base64 encoded and signed JWT. (Newlines should be ignored and are here for display purposes only.)

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJGaXJzdE5hbWUiOiJSb2IiLCAiTGFzdE5hbWUiOiJTdGFuZmllbGQiLCJPY2N1cGF0aW9uIjoiU29mdHdhcmUgRGV2ZWxvcGVyIn0.
541eed57c382b02bfd3674ef0a4d7003c4381d641926c42284266d5c47d91dc6

Verifying a JWT With The Signature

Once we’ve received the JWT, we can use the secret-key to hash and verify that our resulting signature matches the signature in the token. This is done by simply repeating what we did during signing, and then verifying the signature is a match.

Base64 Decoding a JWT

With the JWT verified, we can now base64 decode it for consumption.

echo eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 | base64 --decode
{"typ":"JWT","alg":"HS256"}
echo -n eyJGaXJzdE5hbWUiOiJSb2IiLCAiTGFzdE5hbWUiOiJTdGFuZmllbGQiLCJPY2N1cGF0aW9uIjoiU29mdHdhcmUgRGV2ZWxvcGVyIn0 | base64 --decode
{"FirstName":"Rob", "LastName":"Stanfield","Occupation":"Software Developer"}

Useful Links

JSON Web Token (JWT) IETF RFC7519
JSON Web Signature (JWS) IETF RFC7515
JSON Web Encryption (JWE) IETF RFC7516
JWT Debugger
JWT Code Libraries
Wikipedia: Hash-based message authentication code (HMAC)
Critical vulnerabilities in JSON Web Token libraries
Wikipedia: About Base64 Encoding
The OpenSSL library
Stack Overflow: Why do we use base64?
Bash script code recipe for base64url encoding
Understanding JSON Web Tokens (JWT)

Leave a Reply