How to Secure JWTs in Node.js: A Practical Guide

Learn how to sign, verify, store and rotate JSON Web Tokens safely in Node.js, and avoid the most common JWT security mistakes.

Last updated · 2026-10-01

Why JWT security is easy to get wrong

JSON Web Tokens are popular because they are stateless: the server signs a small JSON payload, hands it to the client, and later verifies the signature instead of looking up a session. That convenience comes with sharp edges. A token that is signed with a weak secret, stored in the wrong place or never expires is effectively a password that anyone can steal and reuse.

This guide walks through a production-ready setup using the widely used jsonwebtoken package, covering algorithm choice, expiry, storage, refresh tokens and revocation.

1. Use a strong secret or an asymmetric key

For HS256, the secret must be long and random — at least 256 bits. A short phrase like 'mysecret' can be brute-forced offline from a single captured token. Generate one with our API key generator and load it from an environment variable, never from source code.

If several services need to verify tokens but only one should issue them, use RS256 or ES256. The issuer keeps the private key; everyone else receives only the public key, so a compromised API cannot mint tokens.

import jwt from "jsonwebtoken";

const token = jwt.sign(
  { sub: user.id, role: user.role },
  process.env.JWT_SECRET,
  { algorithm: "HS256", expiresIn: "15m", issuer: "api.example.com", audience: "web" }
);

2. Always pin the algorithm when verifying

Historic JWT vulnerabilities came from libraries trusting the alg field inside the token. An attacker could set alg to 'none' or switch RS256 to HS256 and sign with the public key. Always pass an explicit allow-list of algorithms and validate issuer and audience.

const payload = jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ["HS256"],
  issuer: "api.example.com",
  audience: "web",
});

3. Keep access tokens short-lived

A JWT cannot be 'logged out' by default — it stays valid until exp. Limit the damage of a leak by issuing access tokens for 5–15 minutes. Use our JWT expiration calculator to sanity-check the exp values you produce.

Pair short access tokens with a long-lived refresh token. The refresh token is a random opaque string stored in your database, so it can be revoked instantly when a user signs out or changes their password.

4. Store tokens where JavaScript cannot read them

localStorage is readable by any script on the page, so a single XSS bug exposes every token. Put the refresh token in an HttpOnly, Secure, SameSite=Strict cookie and keep the access token in memory only. Rotate the refresh token on every use and reject reuse of an old one — that is a strong signal of theft.

res.cookie("rt", refreshToken, {
  httpOnly: true, secure: true, sameSite: "strict",
  path: "/auth/refresh", maxAge: 30 * 24 * 3600 * 1000,
});

5. Don't put secrets in the payload

The payload is only Base64URL encoded. Paste any token into our JWT decoder and you will see everything inside. Include identifiers and roles, never passwords, personal data or internal infrastructure details.

Checklist

Strong secret or asymmetric keys; explicit algorithms on verify; iss and aud validated; access tokens under 15 minutes; refresh tokens rotated and revocable; HttpOnly cookies; no sensitive data in claims; HTTPS everywhere. Follow these and JWTs become a solid, scalable authentication mechanism.

Try the tools from this article

← All tutorials