JWTs: A Deep Dive into Token Security, Vulnerabilities and Best Practices


JSON Web Tokens (JWTs) are the de facto standard for stateless authentication in modern web applications. Their flexibility, however, often leads to fatal misconfigurations. When penetration testers see a JWT, we don't just see a session identifier - we see a cryptographic attack surface.

Key takeaways

  • A JWT is encoded, not encrypted - every claim in the payload is readable by anyone holding the token.
  • Algorithm confusion (RS256 to HS256) and the none algorithm remain the highest-impact signature flaws we find in custom and legacy implementations.
  • HS256 secrets are passwords. A captured token can be cracked offline with Hashcat, with zero further interaction with the target.
  • Client-supplied header parameters such as kid and jku turn signature verification into an injection surface.
  • A valid signature proves nothing about claim validity - exp, iss and aud must be enforced on every request.
  • Keep access tokens in memory and refresh tokens in HttpOnly, Secure, SameSite cookies - never in localStorage.

Anatomy of a JWT: header, payload and signature

A JWT consists of three parts: a header, a payload and a signature, each separated by a dot (.). To a pentester, intercepting a JWT looks like a long string of seemingly random characters. Here is a real-world example:

eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA

The header and payload are just Base64Url-encoded JSON objects. They are merely encoded, not encrypted. Anyone with access to the token can decode and read this data.

JWT structure visualizer

A JWT (technically a JWS - a JSON Web Token "when signed") is composed of three dot-separated sections: Header.Payload.Signature.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbmRyZWoiLCJyb2xlIjoiaGFja2VyIn0.MmM0ZGMxM2Y1NWQ2YTY2YTE
Header · algorithm & token type
{ "alg": "HS256", "typ": "JWT" }
Payload · data
{ "sub": "andrej", "role": "hacker" }
Signature
HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    "secret"
) = MmM0ZGMxM2Y1NWQ2YTY2YTE

The header

The header contains metadata about the token itself, primarily the token type and the cryptographic algorithm used to secure it. Decoding the first part of the token above reveals:

{
    "kid": "9136ddb3-cb0a-4a19-a07e-eadf5a44c8b5",
    "alg": "RS256"
}

The payload

The payload contains the actual "claims" about the user. This is the primary target for manipulation during a security assessment. Decoding the middle section of our example reveals:

{
    "iss": "portswigger",
    "exp": 1648037164,
    "name": "Andrej Šebeň",
    "sub": "andrej",
    "role": "pentester",
    "email": "[email protected]",
    "iat": 1516239022
}

The signature

Because the header and payload can be easily read or modified by anyone, the security of any JWT-based mechanism relies heavily on the cryptographic signature.

The server generating the token hashes the Base64Url-encoded header and payload using a secret signing key. This mechanism guarantees two things:

  1. Because the signature is directly derived from the rest of the token, changing a single byte of the header or payload results in a mismatched signature.
  2. Without knowing the server's secret signing key, it shouldn't be possible to generate the correct signature for a forged header or payload.

What hackers look for in a JWT

Since JWTs are readable by design, attackers treat them as a valuable reconnaissance source rather than as encrypted data. From an offensive perspective, the payload is where the logical flaws usually reside. These are the critical claims we inspect immediately:

Claim Purpose Pentest check
iss (Issuer) Identifies who issued the token. Does the backend actually validate this? A system that accepts tokens issued by an unauthorized third party is vulnerable to token confusion.
sub (Subject) The user or entity the token represents, often a user ID. Insecure Direct Object Reference (IDOR). Can we change this to admin or user_id=1 and bypass authorization?
aud (Audience) Identifies the intended recipient. Can a token minted for one microservice be replayed against a completely different, highly privileged microservice?
exp (Expiration Time) The Unix timestamp at which the token expires. Does the server actually enforce it? We regularly find APIs that accept expired tokens indefinitely.
nbf (Not Before) The time before which the token must not be accepted. Frequently omitted from validation logic entirely.
iat (Issued At) When the token was created. Useful for calculating token lifespans and spotting tokens that never rotate.

Payload reconnaissance: the information disclosure goldmine

Since the payload is visible to anyone holding the token, developers should treat it as public information. During assessments we frequently find unnecessary internal details exposed through custom claims:

  • Internal network mapping: custom claims such as "node_ip": "10.0.4.12", "cluster": "prod-us-east-1a" or "db_host": "internal-db.local" reveal internal network architecture and IP ranges.
  • Framework and identity provider leaks: claim naming conventions reveal the underlying tech stack. Claims such as http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name signal a Microsoft .NET / Entra ID backend, allowing us to target stack-specific vulnerabilities.
  • Personally Identifiable Information (PII): unnecessary exposure of user emails, phone numbers, full names or physical addresses - creating immediate compliance violations (GDPR / CCPA).
  • Tenant and role enumeration: claims such as "tenant_id": "uuid", "is_god_mode": false or "permissions": ["read:profile"] expose the authorization schema, giving us the exact parameter names to target for privilege escalation.

An example of a leaky JWT payload captured in the wild:

{
  "iss": "auth.internal-corp.local",
  "sub": "usr_99812",
  "email": "[email protected]",
  "role": "admin",
  "internal_ip": "10.240.12.88",
  "db_shard": "shard-04-eu",
  "is_admin": true,
  "exp": 1748037164
}

Bearer tokens vs. refresh tokens: architecture and storage

A secure JWT implementation relies on a two-token system: short-lived access tokens (bearer tokens) and long-lived refresh tokens. Storing these incorrectly is the single most common vulnerability we report.

Bearer tokens (access tokens)

  • Access tokens authorize API requests and are intentionally short-lived, typically lasting between 5 and 15 minutes.
  • Store them only in application memory. Avoid localStorage and sessionStorage, as any successful XSS vulnerability can expose them to an attacker.

Refresh tokens

  • Refresh tokens exist solely to obtain new access tokens after expiration.
  • They should be stored in HttpOnly, Secure, SameSite cookies, preventing JavaScript access and significantly reducing token theft through XSS while also helping mitigate CSRF.

The pentester's playbook: common JWT exploits

When auditing a web application, penetration testing teams typically run through the following attack vectors.

A. Signature validation failures

Many JWT vulnerabilities originate from improper signature verification rather than from broken cryptography. Historically, some libraries accepted the none algorithm, allowing attackers to strip the signature entirely while freely modifying claims such as user roles. All major modern libraries reject none by default, but this flaw still surfaces in legacy applications and custom JWT implementations - and it is worth testing whenever you encounter a non-standard stack.

Another classic issue is algorithm confusion. This is one of the most impactful JWT vulnerabilities and is worth understanding precisely.

  • RS256 is an asymmetric algorithm. The server signs tokens with its private key and verifies them with the corresponding public key. The public key is often freely available - exposed via a /.well-known/jwks.json endpoint, for example.
  • HS256 is a symmetric algorithm. The server uses a single shared secret to both sign and verify tokens.

The attack exploits what happens when a server accepts both algorithms without explicitly hardcoding which one it expects:

  1. The attacker obtains the server's public key, often trivially, from the JWKS endpoint.
  2. They modify the token payload - for example, escalating "role": "user" to "role": "admin".
  3. They change the header from "alg": "RS256" to "alg": "HS256".
  4. They re-sign the entire modified token (header and payload) using the public key as the HS256 secret.
  5. The server receives the token, sees alg: HS256, and verifies it using the public key as the HMAC secret. It matches, and the forged token is accepted.
JWT algorithm confusion attack: switching the alg header from RS256 to HS256 and re-signing the token with the server's public key as the HMAC secret

The root cause isn't broken cryptography - it's the server blindly trusting the client-supplied alg header to decide how to verify the token. Normally, changing alg in the header would invalidate the signature, since the signature covers both header and payload. But the attacker re-signs the entire modified token, so the new signature is perfectly valid under the switched algorithm.

Modern libraries have largely eliminated these flaws, but they still appear in legacy applications and custom JWT implementations.

B. Weak signing secrets

Applications using symmetric algorithms such as HS256 are only as secure as the secret used to sign the token.

During a penetration test, a captured JWT can be attacked completely offline using tools like Hashcat or John the Ripper. Weak secrets such as company names, dictionary words or predictable phrases are often cracked within minutes, allowing attackers to generate valid tokens for any user without ever interacting with the target application again.

C. Header parameter injection

JWT headers may contain optional parameters such as kid (Key ID) and jku (JWK Set URL) that help the server determine which signing key should be used during verification. Because these values are supplied by the client, trusting them without proper validation can introduce serious vulnerabilities.

Common attack vectors include:

  • manipulating the jku parameter to reference an attacker-controlled JWKS endpoint;
  • exploiting directory traversal through the kid parameter if keys are loaded directly from the filesystem;
  • abusing SQL injection where the kid value is concatenated into database queries.

While mature JWT libraries handle these scenarios safely, custom verification logic continues to produce findings during security assessments.

D. Claim validation failures

A valid signature does not automatically mean a token should be trusted. Every security-relevant claim must also be validated by the application.

During pentests, we regularly encounter APIs that:

  • accept expired tokens by ignoring the exp claim;
  • fail to validate the intended audience (aud), allowing tokens to be replayed across different services;
  • trust arbitrary issuer (iss) values;
  • rely solely on client-controlled claims such as role, tenant or permissions without enforcing authorization server-side.

These logic flaws often have a greater real-world impact than cryptographic weaknesses, because they allow legitimate tokens to be used in unintended ways.

Modern tooling for JWT security testing

Manual Base64 decoding and signature manipulation are inefficient. Pentesters rely on specialized tools to automate these attacks:

  • JWTAuditor: an excellent, client-side security testing platform tailored for pentesters. It allows for rapid inspection, automated vulnerability detection (such as testing for none algorithms or header injections) and offline brute-forcing without risking the exposure of sensitive client tokens to third-party telemetry.
  • Burp Suite (JWT Editor extension): used heavily during dynamic web testing to manipulate tokens on the fly during HTTP proxy interception.
  • JWT.io: maintained by Auth0, this is the standard tool for quick manual decoding and for verifying token structures and signatures during initial reconnaissance.
  • JWTLens: a lightweight, privacy-first alternative for token inspection. It decodes and verifies tokens entirely in the browser using the native Web Crypto API, meaning your tokens and keys never leave your machine. Because no network requests are made during verification, it is an ideal tool for securely validating algorithms and signatures.

Practical defensive best practices

To keep your application off a pentest report's critical findings list, engineering teams should enforce these core controls:

  • Hardcode expected algorithms: never let the incoming token dictate how it should be verified. Explicitly configure your validation routine to expect a specific algorithm (such as RS256), and reject the none algorithm unconditionally.
  • Enforce full claim validation: a valid signature only proves the token wasn't tampered with; it doesn't mean it's currently valid for the user. Explicitly validate exp (expiration), iss (issuer) and aud (audience) on every single request.
  • Treat symmetric secrets like passwords: if your architecture requires symmetric signing (HS256), your secret key is effectively a root password. Ensure it has a minimum entropy of 256 bits, is generated via a cryptographically secure random number generator, and is stored securely in environment variables or a secrets manager - never hardcoded in source code.
  • Remember: encoding is not encryption. Never store sensitive data, PII or passwords inside the JWT payload. Anyone who intercepts the token can Base64-decode it instantly.
  • Isolate token storage: never store short-lived access tokens in localStorage or sessionStorage, where a single XSS vulnerability can exfiltrate them. Keep access tokens strictly in client-side memory, and place long-lived refresh tokens in secure, HttpOnly, SameSite cookies.

Advanced hardening: the phantom token pattern

For high-security environments, the most effective defense against client-side theft is to never expose the JWT to the frontend at all. Modern enterprise architectures often implement the "phantom token" pattern with these steps:

  1. The authorization server issues a random, opaque session string (instead of a JWT) to the client application.
  2. When the client makes an API request, it sends this opaque string.
  3. The API gateway intercepts the request, validates the opaque token against the auth server (or session store), and swaps it for a fully populated, signed JWT.
  4. The API gateway forwards the actual JWT to internal microservices.

Frequently asked questions about JWT security

01

Is a JWT encrypted?

No. The header and payload of a standard signed JWT (a JWS) are only Base64Url-encoded, not encrypted. Anyone holding the token can decode and read every claim inside it. The signature protects integrity, not confidentiality, so no sensitive data or PII belongs in a JWT payload.

02

What is JWT algorithm confusion?

Algorithm confusion happens when a server lets the token's own alg header decide how the signature is verified. An attacker takes the server's public RSA key, switches the header from RS256 to HS256, modifies the payload and re-signs the whole token using that public key as the HMAC secret. A server that accepts both algorithms verifies the forgery successfully. The fix is to hardcode the expected algorithm on the server.

03

Where should access tokens and refresh tokens be stored in a browser?

Keep short-lived access tokens in application memory only. Store long-lived refresh tokens in HttpOnly, Secure, SameSite cookies so JavaScript cannot read them. Never place either token in localStorage or sessionStorage, where a single XSS vulnerability can exfiltrate them.

04

How do attackers crack HS256 signing secrets?

A captured HS256 token can be attacked entirely offline with tools such as Hashcat or John the Ripper, with no further interaction with the target application. Weak secrets like company names, dictionary words or predictable phrases often fall within minutes, after which the attacker can mint valid tokens for any user. Symmetric secrets need at least 256 bits of entropy from a cryptographically secure generator.

05

Does a valid signature mean a JWT can be trusted?

No. A valid signature only proves the token was not tampered with. The application must still validate every security-relevant claim: exp for expiration, iss for the issuer, aud for the intended audience - and it must never rely on client-supplied claims such as role or permissions without enforcing authorization server-side.

How Haxoris can help

Token security is rarely broken by cryptography - it is broken by configuration. Haxoris can help you find those gaps before an attacker does:

  • Penetration testing of web applications and APIs, with a dedicated focus on authentication and authorization flows.
  • Review of JWT issuance, verification and key management logic, including JWKS handling and key rotation.
  • Offline strength assessment of symmetric signing secrets.
  • Design review of token storage, session lifetimes and refresh-token architecture.

Conclusion

JWTs are not insecure by design. They become insecure the moment an application lets the token decide how it should be validated, or treats a valid signature as a substitute for authorization. Hardcode the algorithm, validate every claim, treat secrets like root passwords and keep tokens out of browser storage - and the majority of the findings in this article disappear from your next pentest report.

Citations and recommended reading

The methodologies and technical details in this post are inspired by and adapted from the following authoritative security resources:

  1. PortSwigger Web Security Academy. JWT Security Vulnerabilities. A detailed breakdown of signature verification flaws and header injections. portswigger.net/web-security/jwt
  2. InfoSec Writeups. JWT Pentesting: A Journey from Token to Takeover. Practical case studies on exploiting token configurations. infosecwriteups.com
  3. Auth0 / JWT.io. Introduction to JSON Web Tokens. Foundational standard documentation on token structures and RFC 7519. jwt.io/introduction
  4. JWTAuditor. Security Testing Tool. Used for payload manipulation and automated vulnerability assessments. jwtauditor.com
Pavol Litauszki

Author

Pavol Litauszki

Don't wait for attackers - reveal your weakest spot with a penetration test now!

Book Now