Symmetric vs Asymmetric Signing
JWT signing algorithms fall into two categories:
- Symmetric (HMAC): HS256, HS384, HS512 — the same secret key is used to both sign and verify. Simple, fast, but requires all verifiers to know the secret.
- Asymmetric (RSA/EC): RS256, RS384, RS512, ES256, ES384, ES512 — a private key signs, a public key verifies. The public key can be shared freely; only the signer needs the private key.
HS256 — Simple Single-Service Auth
import jwt from "jsonwebtoken";
// Sign
const token = jwt.sign(
{ sub: "user123", name: "Jane Doe", iat: Math.floor(Date.now() / 1000) },
process.env.JWT_SECRET, // shared secret
{ algorithm: "HS256", expiresIn: "1h" }
);
// Verify
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ["HS256"] });
Use HS256 when: a single service both issues and verifies tokens — e.g., a monolithic app where the same server signs and validates sessions. Keep the secret long (>32 bytes), random, and stored in an environment variable.
Don't use HS256 when: multiple microservices need to verify tokens — you'd have to share the secret with every service, increasing the attack surface.
RS256 — Multi-Service Auth
import jwt from "jsonwebtoken";
import fs from "fs";
const privateKey = fs.readFileSync("private.pem");
const publicKey = fs.readFileSync("public.pem");
// Sign with private key (auth service only)
const token = jwt.sign({ sub: "user123" }, privateKey, {
algorithm: "RS256",
expiresIn: "1h",
});
// Verify with public key (any service)
const payload = jwt.verify(token, publicKey, { algorithms: ["RS256"] });
Use RS256 when: an auth service signs tokens that multiple other services verify. Distribute the public key freely via a JWKS endpoint; protect the private key only on the auth service.
ES256 — Smaller, Faster RSA Alternative
ES256 uses ECDSA with the P-256 curve. An EC key pair is ~10× smaller than an equivalent-security RSA key, and signing is faster. ES256 produces a 64-byte signature; RS256 produces 256 bytes.
// Generate EC key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", {
namedCurve: "P-256",
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
const token = jwt.sign({ sub: "user123" }, privateKey, { algorithm: "ES256" });
JWKS — Publishing Public Keys
Auth providers publish public keys as JWKS (JSON Web Key Sets) at a well-known endpoint:
GET https://auth.example.com/.well-known/jwks.json
{
"keys": [{
"kty": "RSA",
"kid": "key-2024-01",
"use": "sig",
"n": "...", "e": "AQAB"
}]
}
Services fetch and cache the JWKS, then verify tokens using the key matching the kid in the JWT header. Rotate keys by publishing both old and new until all outstanding tokens expire.
Algorithm Decision Guide
- HS256: Single service, simple setup, internal tokens
- RS256: Distributed verification, existing RSA infrastructure
- ES256: Distributed verification, prefer smaller tokens and faster signing
- EdDSA (Ed25519): Emerging standard, even smaller and faster than EC — not yet in all JWT libraries
Generate and Sign JWTs
Use ToolsVito's JWT Generator to sign HS256 tokens with a custom payload and secret — useful for API testing and debugging.