Hi DEV, this is my first post. I'm a developer who builds websites, WordPress projects and browser extensions, and the topic comes from something I kept running into: JWT secrets that are far too weak.

The rule most people never read

If you sign tokens with HS256, the secret has to be at least as long as the hash output. That is 256 bits, or 32 bytes. This is not a tip, it is in the spec. RFC 7518, section 3.2, says a key of the same size as the hash output or larger MUST be used with HMAC-SHA2 algorithms.

So the same rule applies to the others:

Algorithm Minimum secret
HS256 256 bits (32 bytes)
HS384 384 bits (48 bytes)
HS512 512 bits (64 bytes)
Why a short secret is a real problem

A JWT is signed, not encrypted. Anyone who sees a token can copy it and try guessing the secret offline, as many times as they like, with no rate limit. Tools like hashcat have a mode just for this (-m 16500).

If your secret is secret, changeme or your app's name, it falls in seconds to a normal wordlist. Once someone has the secret, they can sign any token they want, including one that says they are an admin.

Length is not the same as strength

This confuses a lot of people. A 64-character hex string looks long, but it only holds 32 bytes of randomness. Those 256 bits are the real strength, not the number of characters. And a long password you typed yourself is still weaker than 32 random bytes, because humans are predictable.

The secret should come from a cryptographically secure random generator, not from your head.

How to generate one

Pick whatever you already have installed:

bash

OpenSSL

openssl rand -hex 32
js
// Node.js
require('crypto').randomBytes(32).toString('hex');
python

Python

import secrets
secrets.token_hex(32)

All three give you 256 bits of randomness.

What to do with it

Put it in an environment variable and read it from there:

bash
JWT_SECRET=your-generated-value
Never commit it to Git. If it was ever committed, treat it as leaked and replace it.
Use a different secret for each environment (development, staging, production).
Plan for rotation. Changing the secret logs everyone out, so decide how you will handle that before you need to.
Two more mistakes worth fixing

Pin the algorithm when you verify. Do not let the token decide how it should be checked:

js
jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });

Decoding is not verifying. Reading the header and payload of a token tells you nothing about whether it is real. Only checking the signature with your secret does.

A small tool I made for this

I got tired of pasting secrets and tokens into random websites, so I built a free tool that generates HS256, HS384 and HS512 secrets and decodes tokens, all inside your browser with the Web Crypto API. Nothing is uploaded: jwtsecretkey.com

If you use something different, that is fine. The point is the same: 32 random bytes, kept out of your repo.

What is the weakest JWT secret you have ever found in a real project? I would like to hear it.