You might have heard that you shouldn't be using JWT. That advice is correct - you really shouldn't use it. In general, specifications that allow the attacker to choose the algorithm for negotiation have more problems than ones that don't (see TLS). N libraries need to implement M different encryption and decryption algorithms, and an attacker only needs to find a vulnerability in one of them, or a vulnerability in their combination. JWT has seen both of these errors; unlike TLS, it hasn't already been deployed onto billions of devices around the world.
This is a controversial opinion, but implementation errors should lower your opinion of a specification. An error in one implementation means other implementations are more likely to contain the same or different errors. It implies that it's more difficult to correctly implement the spec. JWT implementations have been extremely buggy.
But The Bad Implementations Were Written by Bad Authors
In the 1800's rail cars were coupled by an oval link on one end and a socket on the other. A railway worker would drop a pin down through the socket, keeping the link in place.
The train engineer could not see the coupler at the time of coupling, and the operation was fraught. Many couplers had their hands mangled. Worse, there was no buffer between the cars, and it was easy to get crushed if the coupling missed. Tens of thousands of people died.
Still, the railroads stuck with them because link-and-pin couplers were cheap. You could imagine excuses being made about the people who died, or lost their fingers or hands; they were inattentive, they weren't following the right procedure, bad luck happens and we can't do anything about it, etc.
In 1893 Congress outlawed the link-and-pin coupler and deaths fell by one third within a year, and that's despite the high cost of switching to automatic couplers.
Alternatives
Update, January 2018: PAST is an excellent library implementing many of the suggestions here. Use PAST. Follow its development for use in your language.
What should you be using instead of JWT? That depends on your use case.
I want users to authenticate with a username and secret token
Have them make a request to your server over TLS; you don't need any additional encryption. TLS provides an encryption layer, you don't need any additional encryption or hashing besides TLS.
I want to post a public key and have users send me encrypted messages with it
The technical name for this is asymmetric encryption; only the user with the private key can decrypt the message. This is pretty magical; the magic is that people don't need the private key to send you messages that you can read. It was illegal to ship this technology outside of the US for most of the 90's.
JWT supports public key encryption with RSA, but you don't want to use it for two reasons. One, RSA is notoriously tricky to implement, especially when compared with elliptic curve cryptography. Thomas Ptáček explains:
The weight of correctness/safety in elliptic curve systems falls primarily on cryptographers, who must provide a set of curve parameters optimized for security at a particular performance level; once that happens, there aren't many knobs for implementors to turn that can subvert security. The opposite is true in RSA. Even if you use RSA-OAEP, there are additional parameters to supply and things you have to know to get right.
You don't want the random person implementing your JWT library to be tuning RSA. Two, the algorithm used by JWT doesn't support forward secrecy. With JWT, someone can slurp all of your encrypted messages, and if they get your key later, they can decrypt all of your messages after the fact. With forward secrecy, even if your keys are exposed later, an attacker can't read previous messages.
A better elliptic curve library is Nacl's box
, which only uses one
encryption primitive, and doesn't require any configuration. Or you can have
users send you messages with TLS, which also uses public key encryption.
I want to encrypt some data so third parties can't read it, and then be able to decrypt it later
You might use this for browser cookies (if you don't want the user to be able to read or modify the payload), or for API keys / other secrets that need to be stored at rest.
You don't want to use JWT for this because the payload (the middle part) is unencrypted. You can encrypt the entire JWT object, but if you are using a different, better algorithm to encrypt the JWT token, or the data in it, there's not much point in using JWT.
The best algorithm to use for two-way encryption is Nacl's
secretbox
. Secretbox is not vulnerable to downgrade or protocol
switching attacks and the Go secretbox implementation was written by a
world-renowned cryptographer who also writes and verifies cryptographic
code for Google Chrome.
I want to send some data and have users send it back to me and verify that it hasn't been tampered with
This is the JWT use case. The third part of a JWT is the signature, which is supposed to verify that the header and the payload have not been tampered with since you signed them.
The problem with JWT is the user gets to choose which algorithm to use. In the
past, implementations have allowed users to pass "none" as the verification
algorithm. Other implementations have allowed access by mixing up
RSA and HMAC protocols. In general, implementations are also more
complicated than they need to be because of the need to support multiple
different algorithms. For example in jwt-go
, it's not enough to check err == nil
to verify a good token, you also have to check the Valid
parameter on a
token object. I have seen someone omit the latter check in production.
The one benefit of JWT is a shared standard for specifying a header and a payload. But the server and the client should support only a single algorithm, probably HMAC with SHA-256, and reject all of the others.
If you are rejecting all of the other algorithms, though, you shouldn't leave the code for them lying around in your library. Omitting all of the other algorithms makes it impossible to commit an algorithm confusion error. It also means you can't screw up the implementations of those algorithms.
For fun, I forked jwt-go and ripped out all of the code not related to the HMAC-SHA256 algorithm. That library is currently 2600 lines of Go, and supports four distinct verification algorithms. My fork is only 720 lines and has much simpler API's.
// old func Parse(tokenString string, keyFunc func(*Token) (interface{}, error)) (*Token, error) func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error // new func Parse(tokenString string, key *[32]byte) (*Token, error) func Sign(signingString string, key *[32]byte) string func Verify(signingString, signature string, key *[32]byte) error
These changes increased the type safety and reduced the number of branches in the code. Reducing the number of branches helps reduce the chance of introducing a defect that compromises security.
It's important to note that my experiment is not JWT. When you reduce JWT to a thing that is secure, you give up the "algorithm agility" that is a proud part of the specification.
We should have more "JWT"-adjacent libraries that only attempt to implement a single algorithm, with a 256-bit random key only, for their own sake and for their users. Or we should give up on the idea of JWT.
Liked what you read? I am available for hire.
My platform consists of multiple agents that need to authenticate with multiple distributed services across multiple regions; latency and contention against a central authentication server is a concern. Should I use JWT?
You shouldn’t use JWT. A more specific answer would require a lot more details about the specifics of your situation.
Your observations seem sound. Perhaps you should in fact release a new specification, ASJWT (Actually Secure JWT)? ;) Or perhaps less sarcastically, JWT-256 or JWT-H2O (HMAC 256 Only) or something like that
This proposal was recently sent to the mailing list: https://gist.github.com/paragonie-scott/c88290347c2589b0cd38d8bb6ac27c03
Where in the world did you find the stats on the deaths and subsequent reduction of deaths related to the lock-and-pin couplers?
Train Museum in Sacramento, highly recommend a visit.
Pingback: Exploring REST API Authentication Mechanisms – Lean Java
What’s the good alternative for this scenario:
I want to be able to sign a set of “claims”, and have downstream microservices be able to verify those claims. But I don’t want them to be able to create their own claims (so it needs to be signed asymmetrically).
Just put the claims in a JSON object then use e.g. golang.org/x/crypto/nacl/auth to append an authentication tag. Or use public key encryption, with only one public key allowed to generate claims.
What are your opinions on Macaroons and Paseto ?
Macaroons are really complicated to get started with! Paseto is linked in the post, it’s what you want to replace JWT with in most cases.
Pingback: Look Ma, no JWT! - The Gnar Co.