← All posts

JWT in Node.js: five mistakes that break your API

Base64 is not encryption, alg=none, weak secrets, and refresh without rotation — a pre-production checklist for JWT auth.

JWT in Node.js: five mistakes that break your API
Contents

In brief

JWT in Node.js is often shipped “from a tutorial”: data in the payload, long TTL, and Base64 mistaken for encryption. A Dev.to post walks through common failures — from PII in the payload to refresh tokens without rotation — and outlines a solid access + refresh flow.

What happened

The author opens with a real PR: a developer put CPF and a password hash in the JWT, treating Base64 as protection. The reviewer rejected it — and that pattern is common.

JWT is signing, not encryption: header + payload + signature. Anyone with the token reads the payload. Security depends on the secret/key and signature verification, not on hiding data.

Five frequent failures:

Mistake Risk Fix
Secrets in payload PII leak via XSS/logs IDs only; sensitive data in DB
alg: none Algorithm swap, bypass verify Explicit algorithm allowlist
Weak secret Brute-force signature ≥256 bits, vault, rotation
No exp Eternal access token Short access TTL (minutes)
No aud/iss Foreign service token accepted Check issuer and audience

Refresh tokens need DB storage, rotation on each refresh, a family ID, and revoking the whole family when one refresh is compromised.

Why it matters

Express/Fastify/Nest apps often get auth “from an npm package in an evening.” JWT looks simple, but mistakes rarely show up in unit tests — pentests, log leaks, or middleware bugs expose them.

Refresh without rotation turns a stolen refresh into a long-lived account key. Payloads with email/roles/PII are a gift to attackers on any XSS.

In practice

  1. Access — 5–15 minutes; refresh — days/weeks, httpOnly + Secure + SameSite cookie or a rate-limited endpoint.
  2. In jwt.verify, set algorithms: ['HS256'] (or RS256) — never trust the client header blindly.
  3. Payload: sub, roles/scope, jti — no passwords, documents, or payment data.
  4. Refresh: one-time use + DB record; reuse of the same refresh → revoke the family.
  5. Pre-prod checklist: vault secret, exp/nbf, aud/iss for microservices, never log Authorization.

Takeaway

JWT works as a stateless bearer between services only with discipline: short access, rotated refresh, no secrets in the payload, and strict verify config. The Dev.to article is a useful pre-release checklist for Node APIs.