How to Decode and Verify a JWT Safely

2026-07-14

JSON Web Tokens are compact strings with three dot-separated parts: a header, a payload, and a signature. The first two parts are Base64URL-encoded JSON. That makes them easy to inspect, but decoding a JWT is not verification. Anyone can create or alter a header and payload. Only cryptographic verification establishes that a trusted issuer signed the exact bytes you received.

Use the JWT Debugger to inspect structure while troubleshooting. Treat every displayed claim as untrusted until the signature and application-specific rules pass.

Decode for inspection, not trust

This Node.js snippet decodes the payload without validating it:

function decodePart(part) {
  return JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
}

const [headerPart, payloadPart] = token.split('.');
console.log(decodePart(headerPart));
console.log(decodePart(payloadPart));

This is useful for finding iss, aud, exp, and kid, but it must never grant access. A generic Base64 decoder can also reveal the JSON after translating Base64URL characters, yet it provides no authenticity check.

Verify the signature and claims

Prefer a maintained JOSE library rather than implementing JWT cryptography yourself. For an RS256 token, obtain the issuer's public key through a trusted configuration path, restrict the accepted algorithm, and validate issuer and audience:

import { jwtVerify, createRemoteJWKSet } from 'jose';

const issuer = 'https://identity.example.test/';
const jwks = createRemoteJWKSet(
  new URL('https://identity.example.test/.well-known/jwks.json')
);

const { payload, protectedHeader } = await jwtVerify(token, jwks, {
  algorithms: ['RS256'],
  issuer,
  audience: 'inventory-api',
});

console.log(protectedHeader.kid, payload.sub);

jwtVerify checks the signature and time claims such as exp and nbf when present. Explicit issuer, audience, and algorithms prevent a valid token from another service or an unexpected algorithm from being accepted. Require any business claims your authorization model depends on, and allow only a small, documented clock tolerance.

For HS256, both signer and verifier share one secret. Use a cryptographically random, sufficiently long key delivered through a secret manager—not a password or repository variable:

const key = new TextEncoder().encode(process.env.JWT_HS256_KEY);
const { payload } = await jwtVerify(token, key, {
  algorithms: ['HS256'],
  issuer: 'internal-auth',
  audience: 'worker-api',
});

The HMAC tool is useful for learning how a message and key produce a MAC, but production verification belongs in a vetted library.

Apply a safe validation sequence

  1. Reject missing, oversized, or malformed bearer tokens early.
  2. Parse the protected header only to select an allowed verification path.
  3. Never accept alg: none, and never let the token choose an unrestricted algorithm.
  4. Resolve kid only inside the trusted issuer's key set; do not fetch an arbitrary URL from a token header.
  5. Verify signature, issuer, audience, expiration, and not-before time.
  6. Validate required claims and then perform authorization separately.
  7. Return a generic authentication failure; log a reason without recording the token.

The practical JWT validation recipe helps turn these checks into a repeatable review.

Common mistakes

Base64-decoding and checking exp is not verification. Another common error is confusing authentication with authorization: a correctly signed token can still lack permission for a resource. Avoid putting passwords, API keys, or sensitive personal data in a JWT payload; encoding does not encrypt it. Do not paste production tokens into chat, tickets, or unknown websites. If a token leaks, revoke or rotate the relevant credential where possible and investigate its lifetime and use.

Finally, key rotation must be routine. Cache trusted JWKS responses according to their HTTP policy, handle a newly introduced kid, and retain old public keys only for the intended overlap. Safe JWT handling is a chain: trusted configuration, strict cryptographic verification, precise claim checks, and independent authorization.