← Back to Blog
Security9 min read

JWT Attack Vectors Every Developer Should Know

January 18, 2026

JSON Web Tokens are used in virtually every modern web application for authentication and authorization. Their simplicity is part of the appeal — a self-contained, stateless credential that any service can verify without a database call. That same simplicity, however, has produced a remarkably consistent catalog of implementation mistakes with severe security consequences.

Algorithm Confusion: The "none" Attack and HS256 vs RS256

The JWT specification supports multiple signing algorithms, and the algorithm used is declared in the token's header — a field the client controls. The "none" algorithm attack exploits libraries that honor this declaration: an attacker takes a valid token, changes the alg header to none, modifies the payload to escalate privileges, removes the signature, and submits the resulting token. Vulnerable libraries accept it as valid because a "none" algorithm requires no signature verification. Any library that does not explicitly reject the none algorithm is vulnerable.

The RS256-to-HS256 confusion attack is subtler and arguably more dangerous because it affects applications using asymmetric signing, which developers often assume is inherently more secure. RS256 uses a private key to sign and a public key to verify. HS256 uses a single shared secret for both. If an attacker knows the application's RS256 public key — which is often published at a /.well-known/jwks.json endpoint — they can craft a token signed with HS256 using that public key as the HMAC secret. If the server does not enforce that tokens must use RS256, it may accept the attacker-forged token because it verifies successfully with the public key it already has.

Defense requires libraries to be configured with an explicit allowlist of acceptable algorithms, never derived from the token itself. The verification call should look like jwt.verify(token, publicKey, { algorithms: ['RS256'] }), not jwt.verify(token, publicKey) — the difference between the two is the entire algorithm confusion attack surface.

Weak Signing Secrets and Brute Force

When HS256 is used correctly — with a proper shared secret — the security of the token is entirely determined by the entropy of that secret. Developers routinely use predictable secrets: secret, password, the application name, the company name, or the string "jwt_secret" from a tutorial they never changed. Because the JWT structure is public and the signature is available, offline brute force attacks are trivially parallelizable.

Tools like hashcat and jwt_tool can crack weak HS256 secrets from captured tokens. The attack requires only a single captured token and a wordlist. Rockyou, common password lists, and application-specific wordlists (scraped from the application's source code, domain name, and product names) are all worth attempting. A secret of fewer than 32 random bytes is considered inadequate; anything resembling a human-readable word is crackable in seconds.

The practical fix is using a cryptographically random secret of at least 256 bits, generated by a CSPRNG at application startup or deployment time and stored in a secrets manager — not hardcoded in source code. For applications where key rotation is important, asymmetric algorithms (RS256, ES256) are preferable because the private signing key can be rotated without distributing a new shared secret to every verification service.

JWK Header Injection and the "kid" Parameter

The JWK (JSON Web Key) header injection attack exploits servers that accept a jwk parameter in the JWT header as a trusted key for verification. The JWT specification defines a jwk header parameter to embed the public key used for verification, intended for key discovery scenarios. Vulnerable servers use the key from this header to verify the token's own signature — a circular dependency that allows an attacker to generate a new RSA key pair, sign an arbitrary payload with the private key, embed the public key in the jwk header, and have the server accept it.

The kid (Key ID) parameter tells the server which key to use for verification when multiple keys are in rotation. Implementations that use the kid value directly in a database query or filesystem path are vulnerable to injection. A kid value of ../../dev/null in a filesystem-based implementation would cause the server to verify the token against an empty string — making any signature verify successfully if the library accepts empty secrets. A kid value of key' UNION SELECT 'attacker-controlled-secret in a database-backed implementation is straightforward SQL injection.

Testing for these vulnerabilities requires modifying JWT headers manually. Use jwt_tool or a Burp Suite extension to decode the base64url-encoded header, modify the jwk or kid fields, re-encode, and observe whether the server accepts the modified token. Any acceptance after modification is a finding. Servers should maintain a local, trusted registry of valid keys and ignore any key-related claims in the token header entirely.

Missing Expiration and Token Sidejacking

JWTs without an exp claim are valid indefinitely. A token captured from network traffic, extracted from a log file, found in a bug bounty report, or obtained through any temporary compromise of a client remains valid forever. This violates the principle of credential hygiene — even if a token is compromised, a short expiration window limits the damage window. Tokens for long-lived sessions should use short-lived access tokens (15 minutes is a reasonable upper bound) paired with refresh tokens that can be revoked.

Token sidejacking combines two common issues: insecure storage and missing binding. Tokens stored in localStorage are accessible to any JavaScript running on the page, making them trivially stealable via XSS. Tokens stored in cookies are safer against XSS but require proper HttpOnly, Secure, and SameSite attributes. A sidejacked token extracted from one client can typically be replayed from a completely different IP address, browser, and device — because JWTs, being stateless, carry no binding to the original session context.

Token binding and DPoP (Demonstrating Proof of Possession) are specifications designed to address this. DPoP binds a token to a specific public key held by the client, so a stolen token cannot be replayed without also possessing the corresponding private key. For applications where session token theft is a meaningful threat model, DPoP provides a meaningful additional layer — though library support is still maturing as of 2026.

Claims Validation and Business Logic Flaws

Cryptographic signature verification is only the first half of JWT validation. The claims inside a verified token must also be validated: exp (not expired), nbf (not before — token is not being used prematurely), iss (issuer matches the expected value), aud (audience includes the current service), and sub (subject is a valid user in the current context). Each missed claim check is a potential attack vector.

Audience confusion attacks exploit missing aud validation in microservice architectures. If Service A and Service B both trust the same JWT issuer but do not validate the aud claim, a token issued for Service A can be replayed against Service B. This is particularly relevant when different services have different authorization scopes — a read-only token issued for a reporting service being replayed against a write-capable admin service.

Test claims validation by manually crafting tokens with modified claims using jwt_tool or by writing test cases in your application's own test suite. Verify that your validation code rejects tokens with expired timestamps, wrong issuers, wrong audiences, and future nbf values. These tests should be part of your standard authentication test suite and run in CI/CD — JWT validation logic rarely breaks silently, but it does break during library upgrades and configuration changes.

Implementing JWTs Securely in Practice

Use a well-maintained JWT library rather than implementing JWT parsing yourself. The cryptographic operations and edge cases involved in JWT verification are substantial, and the consequences of getting them wrong are authentication bypasses. Prefer libraries with explicit security advisories tracked against them — a library with published CVEs that were quickly patched is safer than an obscure library with no security history, because it means the library is actively maintained and reviewed.

Configure the library explicitly: set the algorithm allowlist, set the expected issuer and audience, set a maximum acceptable token age independent of the exp claim to protect against extremely long-lived tokens, and reject tokens with missing claims rather than treating them as optional. Never accept algorithm configuration from the token header itself. Treat the JWT verification configuration as security policy — review it at the same cadence as other security controls.

For applications handling sensitive data, consider whether JWTs are actually the right credential model. JWTs trade the overhead of a token store lookup for the inability to instantly revoke credentials. If your application needs to revoke tokens in real time — on logout, on password change, on account suspension — either use short expiration windows and accept the race condition window, maintain a token blocklist in Redis, or use opaque tokens with a token store. The "stateless" property of JWTs is not free; it has concrete security tradeoffs that should be evaluated against your application's threat model.

Stop finding vulnerabilities manually

TigerStrike uses AI agents to continuously discover, validate, and exploit vulnerabilities across your applications — so your team can focus on fixing what matters.