Back to Blog
critical SEVERITY6 min read

JWT Algorithm Confusion: How a Missing Parameter Can Compromise Authentication

A critical authentication vulnerability was discovered where the jsonwebtoken library was being used without explicitly specifying allowed algorithms during token verification. This oversight enables attackers to exploit algorithm confusion attacks, potentially forging valid tokens by manipulating the algorithm header to 'none' or switching from asymmetric to symmetric algorithms, completely bypassing authentication controls.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published March 6, 2026•Reviewed June 3, 2026

Answer Summary

JWT algorithm confusion (CWE-347) is a critical authentication bypass vulnerability in Node.js applications using the jsonwebtoken library. It occurs when `jwt.verify()` is called without specifying the `algorithms` parameter, allowing attackers to manipulate the JWT header's `alg` field to 'none' (disabling signature verification) or switch from RS256 to HS256 (using the public key as HMAC secret). The fix is to explicitly whitelist allowed algorithms: `jwt.verify(token, secret, { algorithms: ['RS256'] })` to prevent token forgery and authentication bypass.

Vulnerability at a Glance

cweCWE-347 (Improper Verification of Cryptographic Signature)
fixExplicitly specify allowed algorithms in verification options: { algorithms: ['RS256'] }
riskComplete authentication bypass allowing attackers to forge valid tokens and impersonate any user
languageNode.js (JavaScript)
root causeMissing `algorithms` parameter in jwt.verify() allows attacker-controlled algorithm selection
vulnerabilityJWT Algorithm Confusion / Algorithm Substitution Attack

Introduction

JSON Web Tokens (JWT) have become the de facto standard for stateless authentication in modern web applications. However, their security depends entirely on proper implementation. A recently patched vulnerability in an application using the jsonwebtoken library (^9.0.2) highlights a common but dangerous oversight: failing to specify allowed algorithms during token verification.

This vulnerability, while seemingly minor in code terms, can have catastrophic consequences—allowing attackers to completely bypass authentication and impersonate any user in your system. If you're using JWTs in your application, this is a must-read.

The Vulnerability Explained

What is Algorithm Confusion?

JWT tokens consist of three parts: a header, a payload, and a signature. The header specifies which cryptographic algorithm was used to sign the token. When verifying a JWT, the server must check both the signature's validity AND ensure the algorithm used is one that's expected and secure.

The vulnerability occurs when jwt.verify() is called without the algorithms parameter:

// VULNERABLE CODE
const decoded = jwt.verify(token, secretOrPublicKey);

Without explicit algorithm specification, the library will trust whatever algorithm is declared in the token's header. This opens two critical attack vectors:

Attack Vector 1: The "None" Algorithm Attack

An attacker can modify a valid JWT's header to use the "none" algorithm, which indicates no signature verification is required:

{
  "alg": "none",
  "typ": "JWT"
}

When the server attempts to verify this token without algorithm restrictions, it may accept the token as valid without checking any signature at all. The attacker simply needs to:

  1. Decode an existing JWT
  2. Change the algorithm header to "none"
  3. Remove the signature portion
  4. Re-encode the token

Result: Complete authentication bypass.

Attack Vector 2: RS256 to HS256 Confusion

Many applications use asymmetric algorithms (like RS256) where tokens are signed with a private key and verified with a public key. If algorithm validation is missing, an attacker can:

  1. Obtain the public key (often publicly accessible)
  2. Create a new token signed with HS256 (symmetric algorithm)
  3. Use the public key as the "secret" for HMAC signing

The server, expecting RS256 but not enforcing it, will use the public key to verify the HMAC signature—which the attacker created using that same public key.

Result: Token forgery with publicly available information.

Real-World Impact

This vulnerability allows attackers to:

  • Impersonate any user by forging tokens with arbitrary user IDs
  • Escalate privileges by adding admin roles to forged tokens
  • Bypass authentication entirely using the "none" algorithm
  • Access sensitive data belonging to other users
  • Perform unauthorized actions on behalf of legitimate users

Example Attack Scenario

Consider an e-commerce application:

  1. Alice registers as a regular user and receives a JWT
  2. Attacker intercepts or obtains Alice's token
  3. Attacker decodes the token and changes the header to "alg": "none"
  4. Attacker modifies the payload: {"userId": "admin", "role": "administrator"}
  5. Attacker re-encodes without a signature
  6. Server verifies token without algorithm check and grants admin access

The attacker now has full administrative control over the application.

The Fix

What Changed

The fix involves adding the algorithms parameter to all jwt.verify() calls throughout the application:

// BEFORE (Vulnerable)
const decoded = jwt.verify(token, secretOrPublicKey);

// AFTER (Secure)
const decoded = jwt.verify(token, secretOrPublicKey, {
  algorithms: ['RS256']  // Explicitly whitelist allowed algorithms
});

How It Solves the Problem

By specifying algorithms: ['RS256'] (or whichever algorithm your application uses), the verification process now:

  1. Rejects "none" algorithm tokens immediately, preventing signature bypass
  2. Prevents algorithm confusion by refusing tokens signed with unexpected algorithms
  3. Enforces cryptographic integrity by ensuring only your chosen secure algorithm is accepted

If an attacker tries to submit a token with "alg": "none" or "alg": "HS256" when only RS256 is allowed, the verification will fail with an error:

JsonWebTokenError: invalid algorithm

Complete Implementation Example

Here's a comprehensive example of secure JWT verification:

const jwt = require('jsonwebtoken');
const fs = require('fs');

// Load your keys securely (example for RS256)
const publicKey = fs.readFileSync('public-key.pem');

function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, publicKey, {
      algorithms: ['RS256'],           // Whitelist allowed algorithms
      issuer: 'your-app.com',          // Verify token issuer
      audience: 'your-api',            // Verify intended audience
      maxAge: '1h'                     // Enforce expiration
    });

    return decoded;
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      throw new Error('Token has expired');
    }
    if (error.name === 'JsonWebTokenError') {
      throw new Error('Invalid token');
    }
    throw error;
  }
}

// Express middleware example
function authenticateJWT(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.substring(7);

  try {
    req.user = verifyToken(token);
    next();
  } catch (error) {
    return res.status(403).json({ error: error.message });
  }
}

Conclusion

The JWT algorithm confusion vulnerability demonstrates that security often fails not from complex exploits, but from simple oversights in implementation. A single missing parameter—algorithms—can completely undermine your authentication system.

Key Takeaways:

  1. Always specify allowed algorithms when verifying JWTs
  2. Use asymmetric algorithms (RS256) for distributed systems
  3. Implement multiple layers of validation beyond just signature verification
  4. Keep security libraries updated and monitor for vulnerabilities
  5. Audit your codebase specifically for JWT verification calls

If you're using the jsonwebtoken library, review your codebase today. Search for all instances of jwt.verify() and ensure they include the algorithms parameter. This simple fix could be the difference between a secure application and a compromised one.

Remember: in security, what you don't specify can hurt you as much as what you do. Write defensive code, assume malicious input, and always validate explicitly.

Have you checked your JWT implementation? Share your experiences or questions in the comments below.


Stay secure, and happy coding!

Prevention and further reading

Related Articles

critical

JWT Authentication Disabled Signature Validation in

A critical misconfiguration in JWT authentication explicitly disabled signature validation, allowing attackers to forge valid tokens with arbitrary claims and bypass authentication entirely. The fix re-enables signature validation on all incoming bearer tokens, restoring the security boundary of the authentication layer.

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How JWT Signature Bypass happens in Node.js and how to fix it

A critical authentication bypass vulnerability was discovered in `backend/services/auth-state.js` where the `tokenTtlSeconds()` function used `jwt.decode()` instead of `jwt.verify()`, allowing attackers to forge JWT tokens with arbitrary claims. Because `jwt.decode()` never validates the cryptographic signature, any attacker could craft a token with a manipulated expiration time or elevated privileges and have it accepted as legitimate. The fix replaces the insecure decode call with `jwt.verify(

critical

How Unverified JWT Decoding Happens in Java and How to Fix It

A critical authentication bypass was discovered in `JwtExtractor.java` where `JWT.decode()` was used instead of a proper signature-verifying method, allowing any attacker to forge a JWT with an arbitrary username — including `admin` — and gain unauthorized access. The fix adds clear documentation establishing the trust boundary: signature validation must occur upstream, and the extracted claims are for display purposes only. This change prevents the class from being misused as an authorization g

critical

How broken authentication happens in Node.js Express APIs and how to fix it

A critical authentication bypass in the `/api/posts` endpoint allowed any unauthenticated user to create, update, or delete posts without verification. The POST endpoint had zero authentication checks, while PUT and DELETE endpoints used a trivially bypassable username comparison that attackers could forge by simply including the target username in their request body. The fix validates user identity by looking up the userId in the database before any post operations.

high

How Authentication Bypass happens in PyJWT and how to fix it

A critical authentication bypass vulnerability in PyJWT 2.12.1 allowed attackers to forge valid JSON Web Tokens, potentially bypassing application authentication mechanisms entirely. The vulnerability was fixed in PyJWT 2.13.0 through security improvements to token validation logic. This fix is essential for any application relying on JWT-based authentication.