Freelance software engineer and application security consultant. PhD in AI. Mainly paid to write Java. Secret Prolog junkie.
JSON Web Tokens (JWTs, pronounced "jots") are gaining in popularity as a way to securely transmit small packets of information, such as session tokens, proof of identity, and network protocol messages. While there are a great many libraries available that make constructing and verifying JWTs straightforward, it is also easy to misuse them and end up compromising the very security properties you wanted to achieve. In this article, I outline some best practices to ensure that your information stays secure.
It is easy to think of cryptography libraries as a black box. You pick a library, read the docs, and before long you have sprinkled a little digital signature or encryption magic dust on your application and moved on to the next task. This is a mistake. Even with the most carefully designed and implemented library, a lack of knowledge of the security properties that it provides and how it provides them can lead to fatal flaws when used incorrectly.
JWT libraries provide a lot of confusing options:
These options can be bewildering at first, and you need to pick carefully to get the security properties you desire. For instance, to protect session tokens issued and consumed by the same application, it may be reasonable to use a symmetric encryption mode such as AES-GCM. This provides both confidentiality of the content of the session token and integrity and authenticity to prevent forgeries. However, a public key encryption scheme such as RSA would be wholly inappropriate in this case, as anybody with the public key can then create a valid session token.
Taking some time to learn about the security properties and how the underlying cryptographic primitives provide them will pay dividends in the long run. The Coursera Crypto I course will give you a good start.
With the plethora of modes described in the previous point, it can be tempting to go overboard and use a bit of everything. This is often unnecessary. For instance, if you want to ensure confidentiality and authenticity of your own JWTs then it is is sufficient to use one of the symmetric encryption modes, as these are already authenticated. You can either directly encrypt the JWTs with a shared key, or use one of the key-wrapping modes. It is certainly not necessary to also then apply a separate HMAC, unless you are very paranoid about security margins. (The better idea is to rotate your keys frequently, see below). The more options you use, the more complex your system becomes to reason about and the more credentials you potentially have to manage.
The weakest part of any system's security is often the key management. If a key is compromised then the security properties are no longer guaranteed: messages may be decrypted, security tokens forged, and other parts of your system breached as a result. You should follow best practices for managing your keys:
A JWT consists of a protected payload together with a plaintext "header" section. This can contain various bits of information such as the algorithms used to sign or encrypt the payload or application-specific information to be used by intermediaries on the network, e.g. for message routing. In a lot of cases, this information is redundant and it is downright dangerous to trust its contents anyway. If you do not need to interoperate with third parties that expect standard JWTs, you can save some space and eliminate a whole class of vulnerabilities by simply stripping off the header section when producing a JWT and then recreate it from known data before parsing. I call these "headless JWTs" and recommend you use them wherever you can.
Stripping the header is easy: just remove everything up to the first "." character in the encoded JWT. To reconstruct the JWT, just base64url-encode a fixed header identifying the known algorithm and parameters and prepend it to the headless JWT.
Combining encryption with compression can cause information leaks if you are not careful. This is not just a theoretical vulnerability as practical attacks have been shown. If an attacker is able to control any information in the same encrypted block as some sensitive information, then compression may leak information about the secret parts. Compressing a JWT can be a big win when space is at a premium (e.g. for HTTP cookies), but it should be weighted up against the potential risks. If you need confidentiality and really want compression too, some options to consider include:
Where JWTs are used to convey authority, best practice is to limit the period of time that the JWT is valid for. If JWTs must be long-lived, then investigate options for revoking JWTs to prevent further use:
Like most RFCs, the JWT specs contain Security Considerations sections that detail common threats and advice on how to avoid them. You should read all of these and make sure you understand them before deploying a JWT-based solution:
You should also read common criticisms of JWT-based solutions. While I believe that article overplays the risks (there are reasonable mitigations available), it makes some valid points that you should consider before betting the farm on JWTs.
I hope this article has given you some solid advice on building robust and secure JWT-based solutions. When used carefully and with plenty of planning, JWTs can form the basis of highly scalable and reliable systems. By following the best practice in this article, you should be able to put appropriate protections in place to ensure that your data stays secure.
This article was originally published on my consulting company blog: Pando Software