JWT or not?
In a recent web portal project, made with Python and Angular, I faced the amethic doubt: should I use the standard pseudorandom generation of session tokens, or should I use a JWT?
Wait, what is a JWT?
A JWT (JSON Web Token) is a standard format for building tokens with claims.
The standard defines a set of mandatory tags, a format (header, payload,
signature) and some other useful things. All those things are encoded in
base64
(of course the signature field is calculated before this step),
concatenated with a dot .
and the result is sent to the user as a token.
Note that, by default, a JWT is signed, not encrypted. So, informations are visible to anyone: do not place sensitive content in a JWT. Instead, you want to use the encrypted version, named JWE (JSON Web Encryption).
More info here: https://jwt.io/
Why we (may) need a JWT?
Old-school session tokens are pseudorandom generated text. Usually they’re
hexadecimal
or base64
outputs from a random sequence of bytes. This string
is used as an unique identifier for a set of server-side parameters, named
“session”. This “token” is sent to the client, which uses it every time it
sends a request to the web server.
A JSON Web Token is a string built starting from three base64
-encoded JSON.
This structure allows us to:
- Include an arbitrary number of parameters (eg. the username) in the payload section of the session token (the “claim”)
- Cryptographically sign the payload, and embed the generated signature in the token
- Specify which algorithms has been used to calculate the signature
JWT detractos says that there are a number of advantages:
- You don’t need a lookup in a database to retrieve the session, as you can have an arbitrary number of (custom) fields inside the payload section of the JWT to describe what you need.
- The claim is verifiable via a strong cryptographic function, so no risk of faked claims
The point 1 is actually quite interesting: one difficulty in a scalable web application is the session database synchronization and performance. Usually, to deal with very high web request loads, the load balancer is configured to forward requests with the same session token to the same backend server, to avoid bottleneck or inconsistency for session data. A JWT may embed data useful to rebuild the session without a lookup in the session database (so, performance improvement and lower number of issues and configurations).
Is that so?
If JWT is good or not, it depends on your context. I’m not a big fan of sending
base64
blobs in HTTP just to identify an user. In fact, I think that the cost
of synchronizing the session database is more manageable than kilobytes of
serialized JSON to parse and verify cryptographically.
But there are also other reasons to not use JWT: first of all, they’re not encrypted, so there is a constant leak of (server-side) informations. This may be a problem or not depending on your project, but I think that one should reduce the information leakage to minimum.
Ok, so we can avoid information leaks using JWE (the encrypted version of JWT). Now the problem is that, for each request, we need to:
- Split a string in three components
- Decode three
base64
fields - Parse JSONs
- Decrypt the payload
- Verify the validity of the token, and other informations
Let’s assume that we can do this faster than a simple hash search and JSON decode. We can face a bigger problem.
The JWT/JWE is meant to be sent in HTTP headers, or in HTTP cookies (which are inside requests/responses headers, so…). Although HTTP RFCs doesn’t set any limit in header size, most webservers have a limit for all the HTTP request and/or response (eg. Apache is limited to 8KB). And cookies usually are limited to less than 4KB.
Even if the encryption doesn’t add a significant number of bytes, you have the
base64
encoding which eats 33.33333% more space than the original text.
Last, but not least, how do you expire a single token before its natural death? In JWT, the only “proposed solution” is to synchronize a blacklist to all webservers, so you can check if the token was blacklisted and discard it. Which is funny, because this will require an hash lookup in a database.
Then, probably, we need to access to the DB anyway. So, what’s the point to decode and checks all those things instead of a simple query?
So JWT is bad…
JWT can be useful if we know in advance that:
- We can wait for the natural death of the token (ie. cannot manually expire it)
- The request rate will be so high that different requests from the same client can be forwarded to different backend workers
- No access to a database is made, or the cost to access to the session database is so high that is better to have the worker encode/decode/verify things (all local functions) instead.
- Third-party integration
The real question that you want to do to yourself is: how big is my project? You’re not Google, so maybe you don’t need to scale to that level. And you can still use your old (pre-JWT) method.
What about UUIDs/GUIDs?
No, they’re not meant to be unique at this level, and they are not random enough (they provide 122-bit randoms, not so many). More info in the official RFC for UUIDs: RFC 4122, section 6.