Home / Tutorials / Authentication

Authentication · Authentication · Auth blocked

Fix: JsonWebTokenError: invalid signature

Error JsonWebTokenError: invalid signature

By the Erzon engineering team · A step-by-step fix, not a forum thread

JsonWebTokenError: invalid signature is thrown by the Node jsonwebtoken library (equivalents exist in every stack) when a JWT’s signature does not match what the verifier computes from the token’s header and payload using its configured key. The token decodes fine; it just was not signed by the key you are checking against. Since users cannot authenticate while this happens, it usually surfaces as a login or API outage. The cause is nearly always key disagreement between the signer and the verifier, not a corrupted token and not an attack.

JsonWebTokenError: invalid signature

What this error means

A JWT is three base64url segments: header, payload, and signature. Verification recomputes the signature over header.payload using the verifier’s key (a shared secret for HS256, a public key for RS256/ES256) and compares it to the third segment. “Invalid signature” means that comparison failed, which reduces to one fact: the key or algorithm the verifier used is not the one the signer used.

That single fact fans out into many operational shapes: a secret that differs between the auth service and the API, an old token surviving a key rotation, a verifier fetching the wrong public key from a JWKS endpoint (or not matching the token’s kid), or a token subtly mangled in transit by quoting, wrapping, or truncation.

Common causes

  • The signing secret differs across services or environments: drifted env vars, a secret with an invisible trailing newline or wrapping quotes, or one side base64-decoding the secret while the other uses it raw.
  • Key rotation without a grace period: tokens signed with the old key hit verifiers that only know the new key.
  • Asymmetric key mixups: verifying with the wrong public key, a stale cached JWKS, or ignoring the token header’s kid when the issuer has multiple keys.
  • Algorithm mismatch: signing HS256 while verifying expects RS256, or vice versa.
  • Tokens from the wrong issuer entirely, such as a staging-issued token sent to production.
  • A mangled token: truncated by a header size limit, URL-encoded twice, or copied with whitespace.

How to fix it

STABILIZE first. If this began right after a deploy or a secret rotation, restore the previous signing key (or roll back the deploy) so existing sessions verify again, then plan the rotation properly with dual keys.

  1. Decode the failing token without verifying, to see who signed it and with what algorithm and key ID. The payload is just base64url:
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | head -c 400
echo "$TOKEN" | cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null

Check iss, iat, and the header’s alg and kid. A token issued by staging, or an iat from before your last key rotation, is the story already.

  1. Prove whether the signer and verifier hold the same key material. Never print secrets; compare hashes in both services:
node -e "console.log(require('crypto').createHash('sha256').update(process.env.JWT_SECRET).digest('hex').slice(0,16))"

DIAGNOSE with those two results. Same hash on both sides but verification fails: look at algorithm settings and whether one side transforms the secret (base64 decode, trimming). Different hashes: you found it; find which environment holds the wrong value and where it came from. Token header kid not present in the verifier’s current JWKS: stale key cache or rotation gap. iat older than the rotation: old tokens dying against a new key, which needs a grace window rather than a config fix.

FIX the identified mismatch, and pin verification down explicitly so the token cannot negotiate:

const jwt = require("jsonwebtoken");

// verifier
jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ["HS256"],      // explicit allowlist, never derived from the token
  issuer: "https://auth.example.com",
});

For rotations, run dual keys for the lifetime of the longest-lived token: sign new tokens with the new key while verifying against both, keyed by kid:

const keys = { "2026-07": NEW_SECRET, "2026-01": OLD_SECRET };
const { kid } = jwt.decode(token, { complete: true }).header;
jwt.verify(token, keys[kid], { algorithms: ["HS256"] });

With RS256 and a JWKS endpoint, use a JWKS client with kid-based lookup and a short cache TTL (libraries like jwks-rsa handle this), so a rotation propagates without redeploying every verifier. If the secret came through an env var, fix it at the source (the secret manager or deploy config), redeploy every consumer, and confirm the hash comparison now matches.

How to prevent it

  • Store the signing key in one secret manager entry that every service references; never hand-copy secrets between .env files.
  • Include a kid in every token you issue, even with HMAC, so rotation is routine instead of an outage.
  • Rotate with overlap: verify old and new keys for at least the max token lifetime, then retire the old key.
  • Pin the algorithm allowlist and issuer in every verify call, and add a startup check that hashes the loaded secret and logs the fingerprint.
  • Add an integration test that issues a token in one service and verifies it in the other, catching drift in CI rather than at login time.

When to call a senior engineer

Call for help when signature failures are intermittent across a fleet (typically some instances holding a stale secret or JWKS cache, which needs fleet-wide evidence to pin down), when a botched rotation has logged out your entire user base and needs a recovery plan, or when your token architecture (shared HMAC secrets sprawled across services) needs moving to asymmetric keys with proper JWKS distribution. Erzon engineers can fingerprint the key material every service actually loaded, find the exact instance and variable that diverged, and set up rotation with overlap so the next key change is a non-event.

Related errors we fix

Still down after this?

Want a senior engineer to just fix it?

Paste this exact error into the form. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Book a fix

← Back to all error fixes

Still down?

Get a senior engineer on it now

Leave your email and a line about what is happening. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Or email [email protected]. Same engineers, same one-hour clock.