JWT Security Best Practices | alg none Attack / Expiration / Signature Verification
JWT (JSON Web Token) is the de facto standard for authentication in modern Web APIs, but <strong>implementing it while misunderstanding the specification can easily introduce vulnerabilities</strong>. This article explains common JWT misuse patterns and best practices to avoid them.
JWT Structure Review
JWT is a format that concatenates three Base64URL-encoded strings with dots.
eyJhbGci... . eyJzdWIi... . SflKxwRJSM...
header payload signature
- <strong>header</strong>: Signature algorithm (<code>alg</code>) and token type (<code>typ</code>)
- <strong>payload</strong>: JSON object of claims (sub / iss / aud / exp / iat / ...)
- <strong>signature</strong>: HMAC / RSA / ECDSA signature over header and payload
You can verify the contents of a JWT using <a href="/ja/tools/jwt/">DevLab's JWT Decoder</a>. Remember that Base64URL is encoding, not <strong>encryption</strong>, so the payload contents can be read by anyone without server signature verification.
Threat 1: alg=none attack
JWT specification includes <code>"alg": "none"</code>, a "no signature" algorithm designation. If an attacker exploits this to modify the payload with a <code>{"alg":"none"}</code> header, and the library accepts it as-is, it becomes a <strong>critical vulnerability allowing impersonation of any user</strong>.
<strong>Countermeasure:</strong>
- <strong>Explicitly whitelist</strong> allowed algorithms during verification
- Pass it as an array like <code>jwt.verify(token, secret, { algorithms: ['HS256'] })</code>
- Libraries around 2015 were vulnerable, but current major libraries have implemented fixes. However, always use the latest version unless you're building from scratch.
// ✗ 悪い例 (アルゴリズム未指定 = ライブラリが alg ヘッダを信頼)
jwt.verify(token, secret);
// ✓ 良い例 (アルゴリズムを固定)
jwt.verify(token, secret, { algorithms: ['HS256'] });
Threat 2: Key confusion attack
An attacker with an RSA public key can <strong>change the algorithm from RS256 to HS256</strong> to force the public key to be treated as a "secret key". If the JWT library trusts the <code>alg</code> field when selecting the verification function, forged tokens signed with HMAC using the public key will be accepted.
<strong>Countermeasure:</strong> Always fix the algorithm on the verification side (same countermeasure as threat 1). Additionally, explicitly distinguish the key type:
- For HS256, pass as <code>Buffer</code>
- For RS256, pass the public key in PEM format
Threat 3: Unverified expiration / indefinite tokens
The <code>exp</code> claim in JWT indicates the expiration time in UNIX seconds, but <strong>it is often ignored during validation</strong>. If a token issued once can be used indefinitely, the damage from a breach cannot be contained.
<strong>Countermeasure:</strong>
- Set <code>exp</code> to a short duration when issuing (15 minutes to 1 hour is recommended for access tokens)
- Always check <code>exp</code> during verification (major libraries do this automatically)
- For long-lived sessions, use the refresh token pattern (short-lived access token + long-lived refresh token + server-side revocation list)
Threat 4: Storing sensitive information in JWT
The JWT payload is just Base64-encoded, and anyone can decode it. Despite this, implementations that <strong>put passwords or credit card numbers in the payload</strong> continue to appear.
<strong>Countermeasure:</strong>
- Include only minimal identifying information in the payload stating 「this user is…」 (sub / user_id / role)
- Store sensitive information in a server-side database and retrieve it from JWT using user_id
- If you absolutely must send sensitive information in a JWT, use <strong>JWE</strong> (encrypted JWT)
Threat 5: Unable to revoke (logout)
When JWT is issued stateless, it cannot be revoked. Even if a user logs out, since the server has no token information, the token "can be used until expiration". Even after a password change, issued JWTs remain valid.
<strong>Countermeasure:</strong>
- Minimize damage window with short <code>exp</code> (15 minutes or less)
- On logout, register the <code>jti</code> (JWT ID) in the server-side <strong>denylist</strong> and verify against it during validation
- For critical events (password change, permission change), store <code>token_version</code> in the user record and verify a match during validation
Threat 6: Weak Secrets
If the HS256 secret is too short, it can be cracked by brute force. Strings like <code>"secret"</code> and <code>"password123"</code> are particularly vulnerable.
<strong>Countermeasure:</strong>
- HS256 should use at least <strong>256 bits (32 bytes)</strong> of random values
- Generate with <code>openssl rand -base64 32</code> or the <a href="/ja/tools/password/">password generation tool</a>
- Never commit secrets to Git. Manage them with environment variables or Secrets Manager
Summary of best practices
- Fix the algorithm on the verification side (<code>algorithms: ['HS256']</code>)
- HS256 secret should be 256 bits or more, RS256 should use RSA with 2048 bits or more
- Access token <code>exp</code> is 15 minutes to 1 hour
- Implementing Long-term Sessions with Refresh Token Pattern
- Do not include sensitive information in the payload (Base64 is not encryption)
- On logout, register <code>jti</code> in the denylist
- Manage secrets with environment variables or Secrets Manager; never commit them
- Store JWT in HttpOnly Cookie, not localStorage, on the client side (XSS countermeasure)
Tools useful for debugging
- <a href="/ja/tools/jwt/">JWT Decoder</a>: visualizes header / payload / signature with explanations of the meaning of each claim
- <a href="/ja/tools/jwt-sign/">JWT Generation & Signing Tool</a>: Sign using Web Crypto API in your browser with HS256 / HS384 / HS512. Convenient for generating test tokens
- <a href="/ja/tools/password/">Password Generator</a>: useful for generating secrets
Summary
JWT is a convenient authentication token if used correctly, but without knowing the specification pitfalls and attack patterns, you risk introducing vulnerabilities into production systems. Understand the 6 threats and countermeasures presented in this article and use the latest version of JWT libraries as a foundation. We recommend regularly reviewing your implementation and monitoring security advisories.