When you decode the header of a JWT token, there are usually just two things in it:
{
"alg": "HS256",
"typ": "JWT"
}
Most beginners look at this and skip right past it. The typ part is obvious. The alg part looks like a technical detail that doesn't matter much.
It really does matter. More than you'd expect.
So What Does alg Actually Mean?
The alg field tells your server which algorithm was used to create the token's signature. When the server verifies the token, it uses that algorithm to check whether the signature is valid.
Think of it like a wax seal on an old letter. The seal proves the letter hasn't been opened or changed. The alg field tells you what kind of stamp was used to make that seal — so you know how to check it.
Without this information, your server wouldn't know how to verify the signature at all.
The Algorithms You'll Actually See
There are quite a few JWT algorithms out there, but in practice most apps use one of these:
| Algorithm | Full Name | Key Type | Best For |
|---|---|---|---|
HS256 |
HMAC + SHA-256 | Shared secret key | Single server / most web apps |
HS384 |
HMAC + SHA-384 | Shared secret key | Slightly stronger HMAC variant |
HS512 |
HMAC + SHA-512 | Shared secret key | High-security HMAC variant |
RS256 |
RSA + SHA-256 | Public/private key pair | Multiple services, microservices |
ES256 |
ECDSA + SHA-256 | Public/private key pair | Modern apps, smaller key size |
For most beginners building a regular web app with a single backend server, HS256 is the right starting point. It's simple, well-supported, fast, and secure when used with a strong secret key.
HS256 vs RS256 — What's the Real Difference?
This is the question most people hit next. Both are secure. But they work very differently.
HS256 uses a single shared secret key. The same key signs the token and verifies it. Simple. But it means every service that needs to verify tokens must have access to that secret key.
RS256 uses a key pair — a private key to sign and a public key to verify. The private key stays on your auth server. The public key can be shared freely with any service that needs to verify tokens.
If you have one server, use HS256. If you have multiple services that all need to verify tokens but shouldn't have your signing key, RS256 is the better fit.
Makes sense, right?
How to Set the Algorithm in Node.js
The jsonwebtoken library defaults to HS256 automatically. But you can be explicit about it, which is always a good idea:
const jwt = require('jsonwebtoken');
// HS256 — default, uses your secret key
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{
algorithm: 'HS256', // explicit is better than implicit
expiresIn: '15m'
}
);
// When verifying, specify which algorithm you expect
jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'] // only accept HS256 — important!
}, (err, decoded) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = decoded;
next();
});
Notice the algorithms: ['HS256'] in the verify options. That array is there for a reason, and it's a really important one.
The alg: none Attack — This One Is Worth Knowing
There's a well-known attack that exploits the alg field directly. It's called the alg: none vulnerability and it's affected real libraries in the past.
Here's how it works. Some older or badly written JWT libraries trusted whatever algorithm the token itself claimed to use. An attacker could take a valid token, change "alg": "HS256" to "alg": "none" in the header, strip the signature entirely, and send it.
The vulnerable server would think: "alg is none, so there's nothing to verify." And it would just... accept the token. No signature check at all.
Completely forged tokens. Full access. No secret key needed.
// What an attacker would craft:
{
"alg": "none", // No algorithm = no signature check
"typ": "JWT"
}
// Payload with whatever claims they want
// No signature at all
Modern libraries like jsonwebtoken reject alg: none by default. But the safest practice is to explicitly tell your verifier which algorithm to accept — and never include none in that list.
// Safe - only accept HS256
jwt.verify(token, secret, { algorithms: ['HS256'] });
// Dangerous - never do this
jwt.verify(token, secret, { algorithms: ['HS256', 'none'] });
That one line of defence closes the door completely.
There's Also an Algorithm Confusion Attack
There's a second attack worth knowing about, especially if you ever move from HS256 to RS256.
RS256 uses a public key to verify tokens. That public key is meant to be shared openly. But some vulnerable servers would accept a token signed with HS256 using the public key as the secret.
An attacker who knows your public key — which you shared openly — could use it as an HS256 secret to sign forged tokens. If your server doesn't lock down which algorithm it accepts, it might verify them successfully.
Again, the fix is the same. Always specify exactly which algorithm your server expects:
// If you use RS256, only accept RS256
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// Never leave algorithms open-ended or accept multiple
// unless you have a very specific reason to
Two lines of code. Both attacks blocked.
Which Algorithm Should You Actually Use?
For most beginners and small to medium apps — HS256. It's fast, simple, well-tested, and perfectly secure with a strong key.
For microservices or systems where multiple services verify tokens but only one signs them — RS256 or ES256.
For anything requiring smaller token sizes and modern cryptography — ES256 is worth considering.
Don't overthink it. Pick HS256, set a strong secret, and move on. You can always change the algorithm later if your needs grow. And when you do change it, generate a fresh secret key to go with it.
I always generate mine at jwtsecretkeygenerator.com — a proper cryptographically random key that's long enough to make HS256 genuinely secure. The algorithm choice only matters if the key backing it is solid. A weak key with a strong algorithm is still a weak key.
One Small Field, Bigger Impact Than It Looks
The alg field is two lines in your JWT header. Most people never think about it. But it directly controls how your token signatures are verified — and ignoring it has caused real security breaches in real applications.
Set your algorithm explicitly. Lock down what your verifier accepts. Never allow none. Use a strong key.
That's all it takes to get this right.
💡 Three Rules for alg: Always set it explicitly when signing → Always specify algorithms: ['HS256'] (or whichever you use) when verifying → Never include 'none' in that list. Follow these three and the common alg-related attacks simply don't apply to your app.