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
nonealgorithm 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
kidandjkuturn signature verification into an injection surface. - A valid signature proves nothing about claim validity -
exp,issandaudmust be enforced on every request. - Keep access tokens in memory and refresh tokens in
HttpOnly,Secure,SameSitecookies - never inlocalStorage.
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.
{ "alg": "HS256", "typ": "JWT" }
{ "sub": "andrej", "role": "hacker" }
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:
- 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.
- 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/namesignal 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": falseor"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
localStorageandsessionStorage, 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,SameSitecookies, 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.jsonendpoint, 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:
- The attacker obtains the server's public key, often trivially, from the JWKS endpoint.
- They modify the token payload - for example, escalating
"role": "user"to"role": "admin". - They change the header from
"alg": "RS256"to"alg": "HS256". - They re-sign the entire modified token (header and payload) using the public key as the HS256 secret.
- 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.
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
jkuparameter to reference an attacker-controlled JWKS endpoint; - exploiting directory traversal through the
kidparameter if keys are loaded directly from the filesystem; - abusing SQL injection where the
kidvalue 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
expclaim; - 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,tenantorpermissionswithout 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
nonealgorithms 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 thenonealgorithm 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) andaud(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
localStorageorsessionStorage, 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,SameSitecookies.
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:
- The authorization server issues a random, opaque session string (instead of a JWT) to the client application.
- When the client makes an API request, it sends this opaque string.
- 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.
- 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:
- PortSwigger Web Security Academy. JWT Security Vulnerabilities. A detailed breakdown of signature verification flaws and header injections. portswigger.net/web-security/jwt
- InfoSec Writeups. JWT Pentesting: A Journey from Token to Takeover. Practical case studies on exploiting token configurations. infosecwriteups.com
- Auth0 / JWT.io. Introduction to JSON Web Tokens. Foundational standard documentation on token structures and RFC 7519. jwt.io/introduction
- JWTAuditor. Security Testing Tool. Used for payload manipulation and automated vulnerability assessments. jwtauditor.com