Skip to content

JWT Security Best Practices | alg none Attack / Expiration / Signature Verification

Category: Authentication / Security
This article is currently available in Japanese only. We are working on translations.

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

  1. Fix the algorithm on the verification side (<code>algorithms: ['HS256']</code>)
  2. HS256 secret should be 256 bits or more, RS256 should use RSA with 2048 bits or more
  3. Access token <code>exp</code> is 15 minutes to 1 hour
  4. Implementing Long-term Sessions with Refresh Token Pattern
  5. Do not include sensitive information in the payload (Base64 is not encryption)
  6. On logout, register <code>jti</code> in the denylist
  7. Manage secrets with environment variables or Secrets Manager; never commit them
  8. 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.