APIs need to verify who you are and what you’re allowed to do. JSON Web Tokens or “jots” provide a standardized means for accomplishing this.
This article explains the underlying mechanics of how JWTs simplify authorization and authentication.
So What’s a JWT?
It’s a tamper resistant JSON object. It might not seem that way at first, given a JWTs strange appearance.
Here’s an example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.lnneNaoem98xYFES3mi2CJJjnMONuWAu-FTWB3XJN14
On the surface, that just looks like a string of nonsense with three dots in it. You might have expected to see a traditional JSON object with some key value pairs attached to it. The reason for the scrambled appearance is that JWT strings are Base64 Encoded. This reduces the risk of encoding issues over a transmission channel.
JavaScript environments enable base64 encoding and decoding via the
atob()
and btoa
global functions.
That explains why a JWT doesn’t immediately look like JSON, but we’re not quite done yet. What’s going on with those three dots in the token?
The Format of a JWT
Text that once appeared to be garbage text was actually just Base64 encoded JSON.
In the case of JWTs, the text is further broken down into 3 sections separated
by a period. The sections follow the format of header.payload.signature
.
You can try this out in your browser with the token from the previous section:
// Grab the JWT shown in the first section...
var myToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.lnneNaoem98xYFES3mi2CJJjnMONuWAu-FTWB3XJN14";
// Split the JWT into 3 seperate parts (header, payload, signature):
var tokenSections = myToken.split(".");
// => ["eyJhb...", "eyJoZ"..., "lnneN..."]
// Decode the Base64 so that it looks like JSON:
// This is using the "atob()" function mentioned above.
// For the sake of clarity, I am not using .map() or .forEach()
tokenSections[0] = atob(tokenSections[0]); // => "{"alg":"HS256","typ":"JWT"}"
tokenSections[1] = atob(tokenSections[1]); // => "{"hello":"world"}"
Let’s take a deeper look at theses 3 sections of the JWT.
Section I: The Header
The first part of our JWT looked like this after decoding:
{
"alg": "HS256",
"typ": "JWT"
}
The first part any JWT is the “header”.
Just like HTTP headers, JWT headers are a storage area for meta data.
In the token above, we’re letting the user know that this is a JWT
("typ" : "JWT"
).
The other header ("alg": "HS256"
) indicates the encryption scheme used to sign
the token. We’ll cover that later in section III.
Section II: The Payload
The next part of a JWT is the “payload”. Like the header, it too is a set of key/value pairs.
JWT users refer to these pairs as “claims”. As the name suggests, it conveys information in a verifiable manner.
JWTs are a sort of “sworn statement” made by your server.
Some example claims:
{
"role": "bank_administrator",
"department": "Committee of Mystery and Secrecy",
"email": "t@g.com"
}
In this example, the server can verify authenticity thanks to cryptographic signing.
Like the header
, it is
Base64 encoded JSON.
Certain claim names are
reserved by the JWT spec,
so be careful! For example, the reserved claim iat
indicates the “issued at”
time of a JWT. See the JWT specification for a full list.
Section III: The Signature
If claims were changeable, they would be of no use to developers. Changing privilege levels or identity is a dangerous situation in web applications.
JWTs are tamper resistant through the use of a cryptographic signature. The
signature is an encrypted version of the header
and payload
joined together
by a .
character.
A JWT is invalid when modified, even by one character. All major JWT libraries provide this verification functionality. Most libraries raise a runtime exception
By using public key encryption, users can verify the integrity of tokens without being able to change them.
Here’s a pseudo code example:
// PSEUDOCODE
var header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
var payload = "eyJoZWxsbyI6IndvcmxkIn0";
var signature = encryption(header + "." + payload);
// Returns:
// => "lnneNaoem98xYFES3mi2CJJjnMONuWAu-FTWB3XJN14"
JWT Considerations
Like any other tool, there are minor “gotchas” to be aware of.
Payloads are human readable. Never store an unencrypted user password in a JWT, or anywhere else for that matter.
The “ALG” header is for reference purposes. Never trust it on the backend.
There have been
many attacks
against the “ALG” claim in the past. The most common is setting the alg
header
to none
. The safest way to avoid this attack is to never trust user provided
ALG
claims.
JWTs are stateless. In theory, you don’t need to store a copy of the token on your server. This eliminates the need for server side token storage. But how do you handle revocation of compromised tokens? Consider adding a “JTI” (token identifier) claim for such use cases.
The JTI claim serves as a unique id for a each token. Providing a unique ID for tokens enables creation of server side whitelists or blacklists.
Consider adding an expiration date. Adding an
EXP
(expiration time) claim
to a token is always a smart idea. The exp
claim is a time stamp (integer)
that the server verifies to ensure a token is still active.
Conclusion
JWTs are one of the easiest authorization solutions out there. It’s worth reiterating some of the key benefits:
JWTs follow a standard. You don’t need to re-invent the authentication wheel for every project. The JWT specification does a great job of keeping things standardized.
JWT claims can be stored on the client, since they are tamper resistant. This is known as “stateless authentication”.
JWTs provide more granular permission levels over traditional string tokens because claims are configurable. You can enclose permission information in the token itself.
Transmission over numerous channels is possible since tokens are base64 encoded. It is possible to place JWTs in HTTP headers, URLs, emails, carrier pigeon, etc.
Further Reading
Drop me a line in the comments if you have any questions. If you enjoyed the article, consider signing up for our non-spammy, once-a-month-only-I-promise newsletter.