JWT Basics
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