JWT Is Not Secure — Until You Understand JWS and JWE
Source: Dev.to
Introduction
JWTs are not secure by default. They are fast, stateless, and powerful, but easy to break if you don’t understand what you’re actually doing. Most developers simply copy‑paste jwt.verify(token, secret) and move on—this is where the problems start.
JWT Basics
JWT stands for JSON Web Token. It is part of JOSE (JSON Object Signing and Encryption). Two concepts matter:
- JWS (JSON Web Signature) – signs data. Anyone can read it, but tampering is detectable.
- JWE (JSON Web Encryption) – encrypts data. Only the intended recipient can read it.
Most JWTs are JWS because you rarely need to hide the payload; you just need proof that it hasn’t been modified.
Token Structure
eyJhbGci... . eyJzdWIi... . 2Xh3n...
│ header │ │ payload │ │ signature │
Each part is base64url‑encoded JSON:
| Part | Content |
|---|---|
| HEADER | { "alg": "HS256" } |
| PAYLOAD | { "sub": "user_123" } |
| SIGNATURE | HMAC(header + payload) |
Visual breakdown:
┌─────────────┐
│ HEADER │ → Algorithm + metadata
├─────────────┤
│ PAYLOAD │ → Your claims (readable!)
├─────────────┤
│ SIGNATURE │ → Proof of integrity
└─────────────┘
The payload is not encrypted. Base64 is an encoding, not encryption; anyone can decode it:
atob("eyJzdWIiOiJ1c2VyXzEyMyJ9"); // {"sub":"user_123"}
Header Details
{
"alg": "HS256",
"kid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
alg– algorithm used for signing (e.g., HS256, RS256, ES256).kid– identifier of the key that signed the token (UUID only; never a file path or URL).jku– URL to fetch keys (never trust this value from the token).
Rule: Do not make security decisions based on header values; attackers control them.
Payload Claims
{
"sub": "user_5839",
"exp": 1640995200,
"iss": "https://auth.yourapp.com",
"aud": "https://api.yourapp.com",
"jti": "a1b2c3d4"
}
sub– subject (user identifier).exp– expiration time (mandatory).iss– issuer.aud– audience.jti– unique token ID (useful for logout/blacklisting).
Never store secrets (passwords, API keys, SSNs) in a JWT.
JWE (Encryption)
When you need confidentiality, JWE encrypts the payload:
header.encrypted_key.iv.ciphertext.tag
When to Use JWE
- Tokens traverse untrusted intermediaries.
- Compliance requires encryption at rest.
When to Skip JWE
- Communication already uses HTTPS (encrypted in transit).
- Both issuer and verifier are under your control.
Most systems don’t need JWE.
Common Pitfalls
Algorithm Confusion
// Vulnerable: algorithm not enforced
const decoded = jwt.verify(token, publicKey);
If a token says "alg": "HS256" but your server expects RS256, an attacker can forge a token.
none Algorithm
A token with "alg": "none" has no signature. Some libraries mistakenly accept it:
eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9.
Unvalidated kid
{ "kid": "../../etc/passwd" }
{ "kid": "http://attacker.com/keys" }
Loading a key based directly on kid without validation can lead to arbitrary file reads or remote key injection.
Untrusted jku
If your server fetches keys from a URL supplied in the token (jku), an attacker can point it to a malicious source.
Validation Best Practices
jwt.verify(token, key, {
algorithms: ['RS256'], // Hard‑code allowed algorithms
issuer: 'https://auth.yourapp.com', // Hard‑code expected issuer
audience: 'https://api.yourapp.com', // Hard‑code expected audience
maxAge: '30m' // Enforce token age
});
- Explicitly set allowed algorithms.
- Validate
iss(issuer) andaud(audience). - Never trust
jkufrom the token; configure JWKS URL server‑side. - Use UUIDs for
kid, not file paths or URLs. - Keep access tokens short (15–30 minutes).
- Use longer refresh tokens with revocation support.
Summary
A JWT is a signed statement. The signature guarantees integrity and authenticity, but it does not guarantee that the token should be accepted. Acceptance depends on proper validation of the claims (exp, iss, aud, etc.).
Most JWT vulnerabilities arise from:
- Trusting input that should be untrusted (e.g., header fields).
- Skipping essential validation steps.
Configure your verification strictly, validate everything, and never assume the library enforces security for you. Understanding what the signature actually proves is the difference between a safe implementation and a broken one.
References
- RFC 7515 – JSON Web Signature (JWS)
- RFC 7516 – JSON Web Encryption (JWE)
- RFC 7519 – JSON Web Token (JWT)