Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 4

JWT Security: Vulnerabilities and Best Practices

Recommended resources

Hello! Welcome back to our cybersecurity course.

In the last lesson, we walked through the JWT authentication flow, establishing that the cryptographic signature is the key to guaranteeing a token's integrity. But what happens if that signature verification process is flawed?

Today, we'll shift our focus from the "how it works" to the "how it breaks." Your goal for this lesson is to identify common JWT security vulnerabilities and learn the best practices to mitigate them. This is a crucial step toward your goal of securely implementing authentication in your own applications. We will explore how attackers try to forge tokens and, more importantly, how you, as a developer, can build robust defenses.

1. The Foundation of JWT Attacks

Before we dive into specific vulnerabilities, let's reinforce a core concept. A JWT's payload is merely Base64 encoded, not encrypted. Anyone who gets a token can read its contents. The entire security model hinges on one thing: the server's ability to verify the signature.

If an attacker can either bypass the signature check or trick the server into using a key they control, they can forge a token with any claims they wish—for example, changing {"role": "user"} to {"role": "admin"}.

Let's watch a quick demonstration of this principle.

Introduction to JWT Attacks

The video 'Introduction to JWT Attacks' by Intigriti provides a foundational look at how JWT security is structured and where flaws can appear. The Python script demo is particularly effective at showing why the signature is so critical.

Watch the video from 03:40 to 06:23. The first part explains that vulnerabilities usually come from flawed implementation, not the JWT standard itself. The second part demonstrates with a Python script how modifying a token's payload invalidates the signature, making the token useless unless the verification step is flawed.

The demo clearly shows a tampered token being rejected. This is the expected, secure behavior. The vulnerabilities we'll discuss next all arise from situations where this verification process fails.


2. A Tour of Common JWT Vulnerabilities

Most JWT vulnerabilities exploit weaknesses in how a server's library or application code handles token verification. Let's explore the most common attack vectors. The following resource provides an excellent, detailed breakdown of these attacks from a security researcher's perspective. We'll use it as a reference as we cover the main points.

The Ultimate Guide to JWT Vulnerabilities and Attacks ...

The article 'The Ultimate Guide to JWT Vulnerabilities and Attacks' from PentesterLab is a comprehensive catalog of JWT attack vectors. We won't read it all, but we will use it to understand several key vulnerabilities.

You can keep this article open as a reference. We'll be discussing the concepts from the first four main sections: 'None Algorithm Attack', 'Trivial Secret (Weak HMAC Keys)', and 'Algorithm Confusion (RSA to HMAC)'.

Vulnerability 1: The none Algorithm Attack

The JWT specification includes an alg header parameter to specify the signing algorithm. For debugging, the spec allows for an algorithm of none, meaning the token is unsigned. If a server is misconfigured to accept tokens with "alg": "none", an attacker can simply:

  1. Take a valid token.
  2. Decode the payload and modify it (e.g., elevate privileges).
  3. Change the header to {"alg": "none", "typ": "JWT"}.
  4. Remove the signature part entirely.

The server, seeing alg: none, might skip signature verification and accept the forged claims as valid. This is a classic and severe misconfiguration. You can read more about it in the "None Algorithm Attack" section of the PentesterLab guide.

Vulnerability 2: Algorithm Confusion (RS256 to HS256)

This is a more subtle but equally devastating attack. It exploits the difference between asymmetric and symmetric signing algorithms.

  • Asymmetric (e.g., RS256): Uses a private key to sign and a public key to verify. The public key can be widely shared.
  • Symmetric (e.g., HS256): Uses the same secret key to both sign and verify. This key must be kept absolutely secret.

The attack works like this:

  1. The server uses RS256 and exposes its public key so clients can verify tokens.
  2. An attacker gets the public key.
  3. They forge a new token, but change the header to "alg": "HS256".
  4. They sign this new token using the server's public key as the HS256 secret.
  5. The server receives the token. Its verification logic sees "alg": "HS256" and—if improperly implemented—uses its own public key as the secret to verify the signature. Since the attacker used the same key to sign, the verification succeeds.

The following video provides an excellent visual explanation of this attack.

JWT Authentication Bypass via Algorithm Confusion

The video 'JWT Authentication Bypass via Algorithm Confusion' by Intigriti walks through exactly how this attack is executed, clarifying the roles of the different keys.

Watch the first three and a half minutes (00:00 - 03:37). Focus on understanding how the server is tricked into using a public key as a symmetric secret.

Vulnerability 3: Weak Secret Keys

When using a symmetric algorithm like HS256, the entire security of the system rests on the strength of the secret key. If the key is weak, guessable, or a common default (e.g., "secret", "password123"), an attacker can launch an offline brute-force attack.

They only need one valid token issued by the server. They can then try millions of potential secrets until they find the one that correctly reproduces the token's signature. Once they have the secret, they can forge any token they want.

Test your understanding!

Consider the Algorithm Confusion and Weak Secret Key attacks. Which one requires the server to be using an asymmetric algorithm like RS256, and which one applies to symmetric algorithms like HS256?

Show answer
  • The Algorithm Confusion attack applies when the server is set up to use an asymmetric algorithm (like RS256) but is tricked into using a symmetric one (HS256). The vulnerability stems from misusing the public key.
  • The Weak Secret Key attack applies when the server is already using a symmetric algorithm (like HS256). The vulnerability is the low complexity of the shared secret itself.

Other vulnerabilities exist, such as injecting malicious values into header parameters like kid (Key ID) or jku (JWK Set URL), but the three we've covered are among the most fundamental.


3. Mitigation: Best Practices for Secure Implementation

Now for the most important part: how do we defend against these attacks? As a developer, your focus should be on following established best practices. Fortunately, many of these are straightforward to implement, especially with modern frameworks like Next.js.

The following article offers a fantastic, developer-focused guide on this topic.

Best Practices in Implementing JWT in Next.js 15

The article 'Best Practices in Implementing JWT in Next.js 15' from Wisp Blog is tailored for developers. It provides actionable advice and code examples for securing JWTs in a Next.js environment.

Please read the following sections: 'Best Practice #1: Secure Token Storage', 'Best Practice #2: Proper Token Verification', 'Best Practice #3: Use Auth.js...', and the summary list under 'Common Pitfalls to Avoid'. Pay attention to how these practices directly counter the vulnerabilities we just discussed.

Let's summarize the key defense strategies:

  1. Enforce a Specific Algorithm: This is the single most important mitigation. On your server, never trust the alg header from the token. Your verification logic should be hardcoded to expect one specific algorithm (e.g., RS256). This single step defeats both the none algorithm and algorithm confusion attacks.

  2. Use Strong, Securely Stored Keys:

    • For HS256, use a long, randomly generated secret key with high entropy.
    • Store your keys and secrets outside of your source code, for example, in environment variables (.env.local). Never commit them to Git.
  3. Validate All Relevant Claims: Beyond verifying the signature, your server must also check the standard claims, especially:

    • exp: Ensure the token has not expired.
    • iss (Issuer): Ensure the token was issued by your server.
    • aud (Audience): Ensure the token was intended for your application.
  4. Use HttpOnly Cookies for Token Storage: Storing JWTs in localStorage makes them accessible to any JavaScript running on your page, opening you up to Cross-Site Scripting (XSS) attacks where a malicious script can steal the token. Storing the JWT in an HttpOnly cookie prevents client-side JavaScript from accessing it, providing a strong defense against XSS-based token theft.

  5. Don't Reinvent the Wheel: Building a secure authentication system from scratch is notoriously difficult. For production applications, you should rely on well-vetted libraries that handle these complexities for you. In the context of Next.js, Auth.js (formerly NextAuth.js) is the community standard. It implements these best practices by default, saving you from common pitfalls. We will be using this library in the next module.

This image provides a great final summary of these key security principles.

Nine JWT Security Best Practices
This chart summarizes nine essential security practices for JWT implementation, covering everything from using strong algorithms and validating claims to secure key management and using HTTPS.

Conclusion

Today we've seen how simple misconfigurations in JWT handling can lead to severe security vulnerabilities. However, we've also learned a clear set of defensive best practices to protect our applications.

Key Takeaways:

  • JWT vulnerabilities almost always stem from flawed verification logic on the server, not the JWT standard itself.
  • Common attacks include the none algorithm attack, algorithm confusion (RS256 -> HS256), and brute-forcing weak secret keys.
  • The most critical mitigation is to always enforce a specific signing algorithm on the server and never trust the alg header from the token.
  • Other essential best practices include using strong, securely-stored keys, validating all claims, using HttpOnly cookies, and relying on battle-tested libraries like Auth.js.

In our next module, we'll shift gears. We've focused on how your own server can authenticate users. But what if you want to let users sign in with an external account, like Google? That's the problem that OAuth 2.0 solves. We'll start by exploring the purpose and benefits of OAuth 2.0 for third-party authentication.