OWASP Web Top 10 — A07

JWT Vulnerabilities

JWT vulnerabilities allow attackers to exploit weak JSON Web Token implementations in modern authentication systems. These flaws can lead to authentication bypass, privilege escalation, and complete account takeover through algorithm confusion, weak signing keys, and claim manipulation.

What Are JWT Vulnerabilities?

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims between two parties. A JWT consists of three Base64-encoded parts separated by dots: the header (algorithm and token type), the payload (claims or user data), and the signature (cryptographic verification). The structure looks like: header.payload.signature. JWTs have become the de facto standard for stateless authentication in modern web and mobile applications, replacing traditional server-side session management.

JWT vulnerabilities arise when applications fail to properly validate tokens, use weak cryptographic algorithms, or mishandle the signing process. Because JWTs are self-contained and carry authentication state within the token itself, any weakness in their implementation can be catastrophic. Unlike session tokens that merely reference server-side data, a compromised JWT gives attackers direct access to forge authentication credentials that the server will trust without question.

The stateless nature of JWTs, while providing scalability benefits, also means there is no central authority to revoke a compromised token before its expiration. This makes proper JWT implementation absolutely critical. Common vulnerabilities include accepting the "none" algorithm (effectively unsigned tokens), algorithm confusion attacks (downgrading RSA to HMAC), weak signing secrets that can be brute-forced, and missing validation of critical claims like expiration time, issuer, or audience.

How It Works

1
Application issues JWT for authentication

When a user successfully authenticates, the server generates a JWT containing user claims (user ID, roles, permissions) and signs it with a secret key or private key. The token is sent to the client, which stores it and includes it in subsequent requests via an Authorization header.

2
Client presents JWT with each request

The client application includes the JWT in the Authorization header as Bearer <token>. The server receives the token and needs to verify its authenticity and integrity before trusting the claims inside it.

3
Attacker exploits weak validation

The attacker identifies a weakness in how the application validates JWTs. This could be accepting unsigned tokens (alg: none), using a weak HMAC secret that can be brute-forced, or failing to verify critical claims. The attacker modifies the JWT header or payload to exploit the vulnerability.

4
Malicious JWT is crafted and submitted

The attacker creates a forged JWT that either bypasses signature verification entirely (using "none" algorithm), uses a downgraded algorithm (RS256 to HS256 using the public key as HMAC secret), or contains tampered claims (elevated privileges, extended expiration, different user ID). The modified token is sent to the server.

5
Server accepts malicious token

Due to improper validation, the server accepts the forged JWT as legitimate. The attacker gains unauthorized access with whatever privileges they encoded in the token payload, effectively bypassing authentication or escalating their privileges to administrator level.

Vulnerable Code Example

Vulnerable — Java / Spring Boot
@RestController
public class AuthController {

    private static final String SECRET = "secret123";

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest req) {
        // Authenticate user...

        // VULNERABLE: Weak HMAC secret
        String token = Jwts.builder()
            .setSubject(req.getUsername())
            .claim("role", "user")
            .signWith(SignatureAlgorithm.HS256, SECRET)
            .compact();

        return ResponseEntity.ok(Map.of("token", token));
    }

    @GetMapping("/protected")
    public ResponseEntity<?> protectedResource(@RequestHeader("Authorization") String auth) {
        String token = auth.replace("Bearer ", "");

        // VULNERABLE: Accepts "none" algorithm
        // No algorithm whitelist, no expiry check, no issuer validation
        Claims claims = Jwts.parser()
            .setSigningKey(SECRET)
            .parseClaimsJws(token)
            .getBody();

        return ResponseEntity.ok(Map.of("user", claims.getSubject()));
    }
}

// Attacker can:
// 1. Brute-force the weak secret "secret123"
// 2. Change algorithm to "none" and remove signature
// 3. Modify role claim from "user" to "admin"
// 4. Create tokens that never expire

Secure Code Example

Secure — Java / Spring Boot (RSA + Validation)
@RestController
public class AuthController {

    @Autowired
    private PrivateKey privateKey; // Strong RSA-4096 key from secure storage

    @Autowired
    private PublicKey publicKey;

    private static final String ISSUER = "https://api.example.com";
    private static final String AUDIENCE = "https://app.example.com";

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest req) {
        // Authenticate user...

        // SECURE: Strong RSA signing with all required claims
        String token = Jwts.builder()
            .setSubject(req.getUsername())
            .setIssuer(ISSUER)
            .setAudience(AUDIENCE)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 900000)) // 15 min
            .claim("role", "user")
            .signWith(privateKey, SignatureAlgorithm.RS256)
            .compact();

        return ResponseEntity.ok(Map.of("token", token));
    }

    @GetMapping("/protected")
    public ResponseEntity<?> protectedResource(@RequestHeader("Authorization") String auth) {
        try {
            String token = auth.replace("Bearer ", "");

            // SECURE: Algorithm whitelist, expiry, issuer, audience validation
            Claims claims = Jwts.parser()
                .setSigningKey(publicKey)
                .requireIssuer(ISSUER)
                .requireAudience(AUDIENCE)
                .setAllowedClockSkewSeconds(60)
                .parseClaimsJws(token)
                .getBody();

            // Additional validation: check token not expired (jjwt does this)
            // JTI validation against blacklist for revocation support

            return ResponseEntity.ok(Map.of("user", claims.getSubject()));
        } catch (ExpiredJwtException | MalformedJwtException |
                 SignatureException | UnsupportedJwtException e) {
            return ResponseEntity.status(401).body("Invalid token");
        }
    }
}

// Strong RSA keys prevent algorithm confusion attacks
// Expiry, issuer, and audience claims prevent token reuse
// Exception handling rejects all invalid tokens

Types of JWT Vulnerabilities

Algorithm Confusion Attacks

The most critical JWT vulnerability. Applications that accept the "none" algorithm allow unsigned tokens, letting attackers forge any token by simply removing the signature. The RS256-to-HS256 downgrade attack exploits libraries that allow switching from asymmetric RSA (RS256) to symmetric HMAC (HS256). The attacker uses the public RSA key (which is public knowledge) as the HMAC secret to sign a forged token, and the server accepts it because it validates using the same public key. Always enforce an algorithm whitelist.

Weak Signing Keys

Using weak HMAC secrets makes JWTs vulnerable to brute-force attacks. Common secrets like "secret", "password", or dictionary words can be cracked in seconds using tools like jwt_tool or hashcat. Once the secret is known, attackers can forge any valid token. Key leakage through hardcoded secrets in source code, exposed environment variables, or public repositories gives attackers immediate access. Always use cryptographically strong random keys (256+ bits) and store them securely in vaults or key management services.

Claim Manipulation

Applications that don't validate critical claims are vulnerable to token tampering. Missing expiry validation allows tokens to be used indefinitely, even after user logout or password changes. Role/privilege escalation occurs when attackers modify claims like "role": "admin" or "isAdmin": true. JWK injection (kid header) exploits the kid (key ID) parameter to point to attacker-controlled keys or files. Always validate exp, nbf, iss, aud, and never trust user-controlled claims without verification.

Impact

Successful exploitation of JWT vulnerabilities can have severe consequences, often resulting in complete system compromise. The stateless nature of JWTs means that once forged, tokens are trusted until expiration.

Authentication bypass

Attackers can forge tokens to impersonate any user without knowing their password. By creating a JWT with the victim's user ID or email in the subject claim, the attacker gains complete access to their account. This is especially devastating in applications that rely solely on JWT for authentication without additional verification.

Privilege escalation

Attackers can modify role or permission claims within the JWT to grant themselves administrative privileges. A regular user can change "role": "user" to "role": "admin" in the payload, re-sign the token (if the secret is weak or algorithm is "none"), and gain full administrative access to the application, including access to sensitive data, user management, and system configuration.

Account takeover

Complete account takeover of any user in the system, including administrator accounts. Once an attacker can forge JWTs, they can access email, change passwords, modify profile information, and perform any action the legitimate user could perform. In e-commerce applications, this includes placing orders, accessing payment methods, and viewing order history.

Token replay and persistence

Forged JWTs remain valid until expiration, even if the vulnerability is patched. If tokens have long expiration times or no expiration at all, attackers maintain persistent access. Because JWTs are stateless, there's no server-side session to invalidate, making it difficult to revoke compromised tokens without implementing a token blacklist or changing signing keys (which invalidates all tokens).

Prevention Checklist

Enforce algorithm whitelist and reject "none"

Configure your JWT library to accept only specific, strong algorithms like RS256 or RS512. Explicitly reject the "none" algorithm and any algorithm not in your whitelist. Use asymmetric algorithms (RSA or ECDSA) for public-facing APIs to prevent algorithm confusion attacks. Never allow the JWT header to dictate which algorithm to use for verification.

Use strong cryptographic keys

For HMAC algorithms, use secrets with at least 256 bits of entropy generated by a cryptographically secure random number generator. For RSA, use key lengths of 2048 bits or higher (4096 bits recommended). Store signing keys in secure key management systems (AWS KMS, HashiCorp Vault, Azure Key Vault) and never hardcode them in source code or commit them to version control.

Validate all critical claims

Always validate exp (expiration), nbf (not before), iss (issuer), and aud (audience) claims. Set reasonable expiration times (15 minutes for access tokens is recommended). Use refresh tokens for longer sessions. Validate that the issuer matches your expected value and that the audience is correct for your application. Allow a small clock skew (30-60 seconds) to account for time synchronization issues.

Implement token revocation mechanism

While JWTs are stateless by design, critical applications should implement a token blacklist or revocation list (using Redis or similar) to handle logout, password changes, and security incidents. Alternatively, keep token lifetimes very short (5-15 minutes) and use refresh tokens with server-side validation. Consider using jti (JWT ID) claim for tracking and revoking specific tokens.

Never trust the JWT header

Do not use the alg header from the JWT to determine which verification algorithm to use, as attackers can modify this value. Do not trust the kid (key ID) parameter without strict validation, as it can be exploited for path traversal or SQL injection. Always use a server-side configuration to specify allowed algorithms and key sources.

Secure token storage and transmission

Store JWTs securely on the client side using httpOnly, secure cookies (preferred for web apps) or secure storage APIs for mobile apps. Never store JWTs in localStorage or sessionStorage where they are vulnerable to XSS attacks. Always transmit JWTs over HTTPS to prevent interception. Consider implementing additional security measures like token binding or proof-of-possession tokens for high-security applications.

Real-World Examples

2015

Auth0 "none" Algorithm

Security researchers discovered that several JWT libraries, including those used by Auth0 and other major authentication providers, accepted the "none" algorithm by default. This allowed attackers to create unsigned tokens that were accepted as valid by applications. The vulnerability affected numerous production systems and led to the development of stricter JWT validation guidelines.

2020

Zoom JWT Key Management

Zoom was found to be using weak JWT implementations in several of their services, including hardcoded signing keys in client-side code and insufficient validation of JWT claims. Attackers could forge tokens to join meetings without authorization or escalate privileges within the platform. The disclosure led to significant security improvements in Zoom's authentication architecture.

2021

Microsoft Azure AD JWT Bypass

A critical vulnerability in Microsoft Azure Active Directory's JWT validation logic allowed attackers to bypass authentication entirely. The flaw involved improper handling of certain edge cases in the aud (audience) claim validation, allowing tokens intended for one application to be accepted by another. Microsoft issued emergency patches and recommended all customers rotate their signing keys.

2022

Okta Algorithm Confusion

Researchers demonstrated an algorithm confusion attack against Okta's JWT implementation where an attacker could downgrade RS256 tokens to HS256 by using the public RSA key as an HMAC secret. This affected multiple Okta customers and highlighted the importance of algorithm whitelisting and proper key type enforcement in JWT libraries. Okta released patches and updated security best practices documentation.

Ready to Test Your Knowledge?

Put what you have learned into practice. Try identifying and fixing JWT vulnerabilities in our interactive coding challenges, or explore more security guides to deepen your understanding.