Guide · 8 min read

JWT validation by language & framework

Decoding a JWT and validating one are two different things. This guide covers what you must check on every token, and how to implement it correctly in six popular runtimes and frameworks.

Concept

Decoding is not validating

Anyone can decode a JWT — the payload is just Base64URL-encoded JSON, not encrypted. Validation is what tells you the token is authentic, unexpired, and intended for your service. You must do all of this server-side with your key material. Never validate a token in the browser.

Decoding only
  • Splits on .
  • Base64URL-decodes each part
  • JSON-parses the payload
  • Anyone can do this — no key needed
Full validation
  • Verifies the signature with your key
  • Checks exp, nbf, iat
  • Checks iss and aud
  • Rejects unexpected algorithms
Requirements

What every validator must check

  1. Signature

    Re-compute the expected signature from the header and payload using your key and the expected algorithm. Compare in constant time. Reject the token if they differ.

  2. Algorithm (alg)

    Always specify which algorithms you accept — never derive it solely from the token header. Reject alg: none and any algorithm you didn't expect. Use an allowlist, not a blocklist.

  3. Expiry (exp) and not-before (nbf)

    Check exp against the current UTC time. Allow a small clock skew (±60 s). If nbf is present, reject tokens presented before that time.

  4. Issuer (iss) and audience (aud)

    Confirm iss matches your expected token issuer and aud includes your service identifier. A valid signature from the wrong issuer should still be rejected.

Examples

Validation in six runtimes

All examples validate an HS256 token, checking signature, expiry, issuer, and audience. Swap the algorithm and key type for RS256 or ES256 as needed.

Use jsonwebtoken — the most widely-used Node.js JWT library. Always pass algorithms explicitly.

// npm install jsonwebtoken
import jwt from 'jsonwebtoken';

function validateToken(token) {
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      issuer:     'https://auth.acme.com',
      audience:   'api.acme.com',
    });
    return { ok: true, payload };
  } catch (err) {
    return { ok: false, error: err.message };
  }
}

// In an Express middleware:
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'No token' });
  const result = validateToken(token);
  if (!result.ok) return res.status(401).json({ error: result.error });
  req.user = result.payload;
  next();
}
Watch out

Common validation mistakes

Don'tTrusting the alg from the token header
DoAlways pass an explicit algorithm allowlist to your library
Don'tSkipping iss / aud checks
DoA valid signature from the wrong issuer is still an invalid token for your service
Don'tUsing a weak or short secret for HS256
DoUse a randomly generated secret of at least 256 bits; prefer RS256 or ES256 for multi-service setups
Don'tValidating tokens client-side in the browser
DoValidate server-side only — the browser must never hold your signing secret
Don'tCatching all errors with a single generic handler
DoDistinguish expired tokens (refresh flow) from invalid tokens (reject immediately)

For a full security threat model, see the Security page. For library recommendations, see Libraries.