JWT Basics

2 minute read

JSON Web Token (JWT) have come in to my life. I like them and you will too… Pronounced “jot”, the short version is that they are cryptographically signed blobs of JSON. They pass data around that can be viewed, but not tampered with. The longer version is that they are an open standard (RFC 7519) that “defines a compact and self-contained way for securely transmitting information between parties as a JSON object”.

JWTs look like:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsIm5hbWUiOiJKb2huIEQuIiwiYWRtaW4iOnRydWV9.AOTcSDyeCX-P5Huzb_Rc9AlHwvWBZlj9E9qZZ9dpI8U

If we were to split that on the “.”, we get:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJ1c2VyX2lkIjoxMjMsIm5hbWUiOiJKb2huIEQuIiwiYWRtaW4iOnRydWV9
AOTcSDyeCX-P5Huzb_Rc9AlHwvWBZlj9E9qZZ9dpI8U

The first string is a JSON header with details about the JWT, the second the JSON payload, the data we care about, and the third is a signature, a hash of the header, the payload, and some secret that can be used to verify that JWT as a whole hasn’t been tampered with. All are encoded using Base64URL.

Fortunately, we can leave the implementation details to others. I’m using the Ruby jwt gem, however there are libraries for everything from Go to Perl(!).

Given a Ruby hash (or anything the response to to_json):

payload = {user_id: 123, name: 'John D.', admin: true }
secret = "I'll never tell!"

I can create a JWT using:

token = JWT.encode payload, secret, 'HS256'
=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsIm5hbWUiOiJKb2huIEQuIiwiYWRtaW4iOnRydWV9.AOTcSDyeCX-P5Huzb_Rc9AlHwvWBZlj9E9qZZ9dpI8U"

The token can now be shared, but not changed. It’s contents can be viewed by decoding the JWT:

decoded_jwt = JWT.decode token, secert, true, { algorithm: 'HS256' }

(The true is the verify argument, and we are using the options has to specify what hashing algorithm we are expecting (see below).)

JWTs decode as an array that contains the payload as the first element and some header information about the JWT as the second:

[
    {
        "user_id" => 123,
        "name" => "John D.",
        "admin" => true
    },
    {
        "typ" => "JWT",
        "alg" => "HS256"
    }
]

So, your payload is token.first.

It’s important to understand that the JWT is not encrypted, you can use a blank password and set verify to false and read it:

decoded_jwt = JWT.decode token, '', false, { algorithm: 'HS256' }

JWTs aren’t for keeping secrets. There’s another standard JSON Web Encryption (JWE) for that, we’ll look at another time.

What are they for? Authentication and safely storing state information. If I pass a JWT with the above payload to client, and it passes it back, I can verify that it hasn’t changed, so I can trust the user_id, I set it after all.

How do you use it? I’ll cover that next time!

But, before I go, we need have a little talk about security.

Notice the JWT header above includes the signing algorithm, HS256. This can be detected by the JWT library and used when decoding.

decoded_jwt = JWT.decode token, secert, true

However, it’s bad form to depend on this.

One early attack was to generate a new, unsigned JWT (algorithm == 'NONE') and pass that back to the app. Libraries would ignore the password when decoding an unsigned JWT and it would appear to be valid. Most libraries now detect this condition (using a password with NONE) and raise an error. However, there are other possibly attacks that work by subverting the expected algorithm, so always specify the algorithm.

Comments