A JWT (JSON Web Token) is a compact, signed bearer token format that carries authenticated information between two systems, typically a server and a web or mobile client. The most common use is authentication: the server issues a JWT when the user logs in, the client stores it and sends it back on every subsequent request, and the server verifies the signature to confirm the token is still valid without keeping an active database session. It is standardised in RFC 7519 and underpins OAuth 2.0, OpenID Connect and most modern API architectures.
This guide covers what a JWT is, the internal structure, the common signing algorithms, the bugs most often exploited in penetration testing (alg=none, kid injection, weak secret, JWT confusion), how it compares with traditional sessions and the defensive practices any serious audit expects to see in place.
What a JWT is and what it is used for
A JWT is an ASCII text string that encodes structured information (claims) and signs it with a secret or a private key. The receiver verifies the signature and, if it is valid, trusts the claims.
Typical use cases:
- Authentication. After login, the server issues a JWT that the client sends in the
Authorization: Bearer <token>header on every subsequent request. - Authorisation. The JWT carries the user roles or permissions; the server decides what the user can do without going back to the database.
- Service-to-service exchange. In microservice architectures, one service signs a JWT to authenticate against another.
- Single Sign-On (SSO). The identity provider issues a JWT that works across all federated services (OpenID Connect). In corporate legacy environments, the XML-based alternative is SAML. Modern AitM phishing kits target exactly the SSO flow to capture tokens.
What makes JWT useful is not the cryptography itself (it is standard), but the compact self-contained format: it fits in a cookie or an HTTP header, transmits efficiently and allows stateless verification on the server.
Structure of a JWT: header, payload, signature
A JWT is three blocks separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6ImFkbWluIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each block is JSON encoded in Base64URL.
Header
Defines the signing algorithm and the token type. Decoded example:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key-2026-01"
}
Typical fields: alg (algorithm, mandatory), typ (type, optional), kid (key ID, identifies the key used when there is rotation).
Payload
Contains the claims (statements about the user or the session). There are registered claims (sub, iss, aud, exp, iat, nbf, jti) and custom application claims.
{
"sub": "1234",
"role": "admin",
"exp": 1747900800,
"iat": 1747897200
}
Important: the payload is not encrypted, only encoded. Anyone holding the token can read its contents. Do not put secrets, passwords or sensitive data that should not be visible to the client.
Signature
The cryptographic signature of the concatenated header and payload. It is computed with:
HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)
for symmetric algorithms (HS256), or with an RSA/ECDSA private key for asymmetric algorithms (RS256, ES256).
The signature is what guarantees that neither header nor payload have been modified after issuance. Without a valid signature, the server rejects the token.
Common signing algorithms
The algorithms most used in production and their operational differences:
| Algorithm | Type | Signature size | Typical use |
|---|---|---|---|
| HS256 | Symmetric (HMAC + SHA-256) | 256 bits | Monolithic apps, same service signs and verifies |
| HS384 / HS512 | Symmetric, longer hash | 384/512 bits | Same as HS256, larger security margin |
| RS256 | Asymmetric (RSA + SHA-256) | 2048-bit key | Microservices, SSO, public APIs |
| RS384 / RS512 | Asymmetric, longer hash | 2048+ bit key | Same as RS256, extra security |
| ES256 | Asymmetric (ECDSA P-256 + SHA-256) | 64 bytes signature | Compact tokens, same security level as RS256 |
| ES384 / ES512 | ECDSA with larger curves | 96/132 bytes | Same as ES256 with margin |
| EdDSA | Ed25519 curve | 64 bytes | Modern, recommended where supported |
| none | No signature | 0 | Never use in production (known attack vector) |
Quick decision: HS256 if the same service signs and verifies; RS256 or ES256 if there is a separation between issuer and verifier.
Common JWT bugs that appear in penetration testing
This is the category that most often shows up in penetration test reports on APIs and web applications:
1. alg: none
The RFC allows the none algorithm for unsigned tokens. Poorly configured libraries accept tokens with alg: none in the header and validate them without checking the signature. The attacker drops the signature and modifies the payload freely:
{"alg": "none", "typ": "JWT"}.{"sub": "victim", "role": "admin"}.
Defense: explicitly reject alg: none in the validation code. Configure a whitelist of accepted algorithms.
2. Asymmetric algorithm treated as symmetric
If the server is configured to validate RS256 but the library uses the public key as the HS256 secret, an attacker can sign tokens with the public key (which is accessible) and the server accepts them as valid HMAC. Historical vulnerability across many libraries; still shows up in legacy code.
Defense: validate that the header algorithm matches the algorithm expected for that key.
3. Weak secret in HS256
If the HMAC secret is short or predictable (a memorable phrase, "secret", password123), an attacker captures a token, extracts the signature and cracks it offline with hashcat or john the ripper in minutes to hours. Once the secret is cracked, the attacker can sign any token.
Defense: secret of at least 32 random bytes (64 recommended). Generate with openssl rand -base64 64.
4. Injection in kid
The kid field (key ID) selects the specific key the server uses to verify. If the code queries kid against a database without sanitisation, it opens SQL injection. If it reads a local file (/keys/{kid}.pem), it opens path traversal:
"kid": "../../../../dev/null"
Some exploits make the server validate the signature against a known system file, letting the attacker predict the signature.
Defense: validate kid against a whitelist, never use it directly in queries or paths.
5. JWT confusion across domains
If iss (issuer) or aud (audience) are not validated, a token issued for one application can be reused in another from the same provider.
Defense: always validate iss and aud against the value expected by the specific application.
6. Tokens without expiration or eternal expiration
Without exp or with exp very far away (years), a stolen token remains valid forever. Combined with insecure localStorage storage, a single XSS exfiltrates the token and the attacker keeps indefinite access.
Defense: short exp (15-60 minutes for access tokens), refresh tokens with a longer life managed separately, revocation handled via blacklist or key rotation.
7. HS256 signature with the word "secret" as the key
This still shows up in production code more often than it should. Defense: systematic configuration review before going to production, plus a SAST scanner with a specific rule.
JWT compared to a traditional cookie session
The two approaches have real tradeoffs:
| Feature | JWT | Traditional session |
|---|---|---|
| Server storage | Stateless | State in DB/cache |
| Horizontal scaling | Trivial (any instance validates) | Requires shared session store |
| Revocation | Difficult (valid until exp) | Immediate (delete the DB entry) |
| Size | 0.5-2 KB per request | Small cookie (32-128 bytes) |
| Client risk | Token contains info, requires protection | Opaque cookie, less info exposed |
| Renewal | Refresh token + rotation | Implicit renewal |
A reasonable decision:
- JWT: public APIs, microservices, SSO, mobile apps. The defensive effort is worth it.
- Traditional session: monolithic web applications with a single server or a few instances behind a load balancer. Simpler, fewer attack vectors.
JWT security best practices
The ones any audit expects to find in place:
- Algorithm whitelist. Strict validation of
alg. Rejectnone. Do not mix symmetric and asymmetric. - Strong secret. At least 32 random bytes for HS256, managed in a KMS or secret manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault).
- Key rotation.
kidversions the key; the server keeps a set of active keys plus recent history. Rotate at least every 90 days. - Validate
iss,aud,exp,iat,nbf. Each one blocks a vector. It is not optional. - Short
exp. 15-60 minutes for access tokens. Refresh token managed separately. - Client storage. HttpOnly cookies with SameSite=Strict beat localStorage (which is reachable from XSS). If localStorage is used, assume an XSS steals the token.
- No secrets or sensitive data in the payload. It is readable by anyone holding the token.
- Operational revocation. Token blacklist, immediate key rotation in case of compromise, logout that invalidates refresh tokens.
- Logs without tokens. Do not log the full JWT. If debugging is needed, log only
subandjti. - Periodic external audit. Penetration testing that explicitly covers JWT against OWASP API Security Top 10 category 2 (Broken Authentication).
JWT and compliance
- PCI DSS v4.0 (req. 8 and 6.4.6). Token management is part of the strong authentication required on the CDE. Any significant change requires technical testing.
- GDPR (article 32). Appropriate technical measures. Tokens without expiration or revocation make the right to erasure harder to enforce.
- NIS2 (article 21). Effectiveness of technical measures in authentication.
- eIDAS 2 (future EU Digital Identity Wallet). Heavy use of signed claims; JWT and Verifiable Credentials formats overlap.
Frequently asked questions
Is JWT secure on its own?
Yes, when implemented correctly. The standard is solid (RFC 7519). Problems almost always come from implementation: alg: none allowed, weak secret, aud/iss/exp not validated, or insecure client storage. Poorly implemented JWT is one of the most frequent bug categories in API pentests.
What is the difference between JWT and OAuth 2.0?
OAuth 2.0 is an authorisation protocol (how a token is obtained); JWT is a token format (what it looks like). OAuth 2.0 can use JWT as the format or use opaque tokens. OpenID Connect (the identity layer on top of OAuth 2.0) does require JWT for id_token.
Should I encrypt the payload (JWE) instead of just signing it (JWS)?
JWS (signature) is enough by default. JWE (encryption) only makes sense if the payload contains sensitive information that should not reach the client, which is usually a sign the data should not be in the token in the first place. Practical rule: keep identifiers and permissions in the payload, sensitive data outside the token. The signature is then sufficient.
How long should a JWT live?
For access tokens: 15-60 minutes as a starting point. Longer tokens amplify the damage of theft. For refresh tokens: days or weeks, stored in HttpOnly cookies or in a backend with rotation on use.
How do you revoke a JWT before its expiration?
Three approaches: (1) blacklist in cache (Redis, Memcached) with revoked jti until exp; (2) per-user versioning (a counter in the database compared against a token claim); (3) signing key rotation when there is a massive compromise. Granular revocation is the main reason some teams prefer traditional sessions over JWT.
Where do I store the JWT on the client: cookie or localStorage?
HttpOnly + Secure + SameSite=Strict cookie for traditional web applications. localStorage only when architecture demands it (some mobile SDKs), accepting that an XSS exfiltrates the token. Never sessionStorage for persistent tokens.
Related resources
- What is a penetration test: operational definition, methodology and how it relates to JWT auditing.
- OWASP Top 10 2025 business vulnerabilities: category A07 Identification and Authentication Failures covers most JWT bugs.
- Penetration testing pricing in Spain: how an audit covering JWT gets scoped and priced.
JWT at Secra
At Secra we audit JWT implementations as a routine part of our web, mobile and API penetration testing. We cover algorithm analysis, secret management, claim validation, client storage, rotation and revocation. If your organisation needs to audit the JWT implementation in a critical application before a release or a PCI/NIS2 certification, get in touch through contact or check the web and mobile application audit service.
About the author
Secra Solutions team
Ethical hackers with OSCP, OSEP, OSWE, CRTO, CRTL and CARTE certifications, 7+ years of experience in offensive cybersecurity, and authors of CVE-2025-40652 and CVE-2023-3512.