Back to Help Center
DEVELOPER September 11, 2026 · 8 min read

How to Verify a JWT Signature (HS256, RS256, ES256)

JWT signature verification requires the correct key AND rigorous validation of alg, exp, iss, and aud. Getting any of these wrong opens the door to forgery.

JWT signature verification looks simple — the library does the crypto. The security holes are in how you configure it: which algorithm you accept, whether you check expiration, whether you validate issuer and audience.

Prerequisites

  • The JWT token
  • The verification key (secret for HS*, public key for RS*/ES*)
  • Expected values for iss, aud, and other claims

HS256 (Shared Secret) — Node.js

const jwt = require('jsonwebtoken');

try {
  const decoded = jwt.verify(token, SHARED_SECRET, {
    algorithms: ['HS256'],           // Allowlist — critical
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
    clockTolerance: 30,               // seconds for clock skew
  });
  // decoded contains verified claims
} catch (err) {
  // TokenExpiredError, JsonWebTokenError, NotBeforeError
  console.error('Verification failed:', err.message);
}

RS256 (Public Key) — Node.js

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.getPublicKey());
  });
}

jwt.verify(token, getKey, {
  algorithms: ['RS256'],
  issuer: 'https://auth.example.com',
  audience: 'https://api.example.com',
}, (err, decoded) => {
  if (err) return console.error(err);
  // decoded is verified
});

Python (PyJWT + jwks-fetching)

import jwt
from jwt import PyJWKClient

jwks_client = PyJWKClient('https://auth.example.com/.well-known/jwks.json')

try:
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    decoded = jwt.decode(
        token,
        signing_key.key,
        algorithms=['RS256'],
        issuer='https://auth.example.com',
        audience='https://api.example.com',
        leeway=30,  # clock skew tolerance
    )
except jwt.InvalidTokenError as e:
    print(f'Verification failed: {e}')

The Non-Negotiable Checks

Every verification must include:

  1. algorithms=['RS256'] (or whatever you expect) — allowlist explicitly. NEVER trust the alg field from the JWT header alone. The alg: none attack and the RS256/HS256 confusion attack both exploit this.
  2. Signature verification — the library does this automatically when given the correct key
  3. exp (expiration) — libraries check by default; do not disable
  4. nbf (not before) — libraries check by default
  5. iss (issuer) — required. Pin to the expected issuer.
  6. aud (audience) — required for OAuth. Pin to your service identifier.

Timing Attack Prevention

Library comparison functions (jwt.verify, jwt.decode) use constant-time comparison internally. Never write your own:

// BAD:
if (calculatedSig === token.signature) { ... }
// GOOD:
const crypto = require('crypto');
crypto.timingSafeEqual(Buffer.from(calculatedSig), Buffer.from(token.signature));

Inspect Before Coding

Before writing verification code, decode a sample JWT with the JWT decoder to see:

  • Which alg the issuer uses (matches your allowlist)
  • What iss and aud claims look like
  • What custom claims exist

Common Miss

  • Not restricting algorithms — attackers pick none or swap RS256 for HS256 (using the RS256 public key as an HMAC secret)
  • Skipping iss / aud validation — cross-service token replay
  • Not handling clock skew — brief exp failures during clock drift
  • Assuming decoded = verified — decoding never verifies; must call verify() explicitly
  • Storing symmetric keys in code / logs — treat HS256 secrets like passwords

Read what a JWT is, check the JWT glossary entry, and see the JWK entry for key-discovery details.

Read what a JWT is, check the JWT glossary entry, and see the JWS entry for signature-format details.