How to Securely Design Your JWT Library

Published: 10 Dec 2024

I've read the source code of many JWT libraries—some might say, too many. In doing so, I've seen patterns of both effective security practices and common pitfalls. JWT (JSON Web Token) is a widely used standard for securely transmitting information between parties, but the implementation details can make or break its security. Here's a guide on how to design a secure JWT library.

1. Disable the None Algorithm by Default

One of the simplest but most crucial things you can do to secure your JWT library is to disable the None algorithm by default. Allowing users to specify None can lead to a situation where an attacker simply removes the signature and still gets the token validated as legitimate. The None algorithm effectively removes authenticity verification, which defeats the purpose of using JWTs in the first place.

2. Force Users to Pick an Algorithm

Never make assumptions about the cryptographic algorithm that should be used. Do not trust the value coming from the JWT header. Force users to explicitly pick one (or multiple) algorithm(s) for signing and verifying tokens. This approach reduces the likelihood of a developer relying on insecure defaults and it also mitigates the risk of attacks like the None algorithm attack and algorithm confusion attacks.

3. No Standalone Decode Method

JWT tokens must be both decoded and validated. To make security the default, never offer a standalone decode method without validation. This can lead to scenarios where developers unintentionally use the JWT library insecurely by decoding tokens without verifying them. Always ensure that tokens are both validated and decoded together to prevent these vulnerabilities. If someone wants to bypass validation, they should have to go out of their way to do it: Make Secure the Default and Insecure Obvious.

4. Throw Exceptions on Validation Failures

When validation fails, if your programming language supports it, throw an exception. Don't leave any ambiguity or silent failures. This is crucial because silently ignoring validation failures or relying on a return value being checked can lead developers to mistakenly trust malicious tokens, resulting in vulnerabilities.

5. Enforce Time-Based Claims By Default

JWTs often include time-based claims such as exp (expiration), nbf (not before), and iat (issued at). These claims should be enforced by default. If a token has expired or is not yet valid, fail hard and fast. When generating a token, always include one of these claims; you don’t want to sign tokens that remain valid indefinitely.

6. Enforce Complexity for Secrets and Keys

Tokens are only as secure as their signing keys. Enforce strong complexity requirements for secrets and keys used to sign JWTs. Using weak secrets (e.g., secret or password) makes tokens vulnerable to brute-force or dictionary attacks. Keep in mind that secrets used to sign tokens are susceptible to offline brute-force attacks using tools such as HashCat.

7. Use Built-in Cryptography

Where possible, leverage the built-in cryptographic functionality provided by the programming language. This makes your code more portable and consistent with the ecosystem. If no built-in cryptographic functionality exists, use well-established libraries like OpenSSL or BouncyCastle. These libraries have been thoroughly vetted by the security community and will save you from implementing your own crypto, which is likely to result in significant security vulnerabilities.

8. Keep It Simple

Complexity is often the enemy of security. Keep your JWT library as simple as possible. Avoid adding unnecessary features or clever shortcuts that might introduce vulnerabilities. Write simple, readable code that is easy to maintain and audit. Security often comes down to being able to trust that the code does exactly what it says—nothing more, nothing less.

Final Thoughts

Designing libraries is all about making secure practices easy and insecure practices difficult or impossible. By focusing on these core principles, you can create a library that serves developers well and protects the integrity of their applications. Security is a shared responsibility between library creators and users, but the more proactive you are about setting secure defaults, and error-proofing your library, the less likely it is that your library will be the source of vulnerabilities.

If you are interested in hands-on practice, explore our JWT Security Labs to enhance your understanding of these principles in action.

Photo of Louis Nyffenegger
Written by Louis Nyffenegger
Founder and CEO @PentesterLab