JWT signing algorithms: HS256, RS256, ES256, EdDSA
The alg header names the scheme that produced a JWT's signature. Every registered signing algorithm belongs to one of four families, and the choice that actually matters is symmetric vs asymmetric — everything after that is key size and ecosystem support.
| Algorithm | Family | Key material | Notes |
|---|---|---|---|
| HS256 / HS384 / HS512 | HMAC (symmetric) | One shared secret | Whoever can verify can also forge |
| RS256 / RS384 / RS512 | RSA PKCS#1 v1.5 | Private + public key | The de-facto default of every identity provider |
| PS256 / PS384 / PS512 | RSA-PSS | Private + public key | Modern RSA padding; prefer over RS* where supported |
| ES256 / ES384 / ES512 | ECDSA | EC private + public key | Far smaller keys and signatures than RSA |
| EdDSA (Ed25519) | Edwards-curve | Private + public key | Fast, compact, misuse-resistant — best modern choice |
HS256 vs RS256 — the decision that matters
HS256 signs and verifies with the same secret. That's fine when one service both issues and consumes its own tokens, and fatal the moment a second service needs to verify: you'd have to hand it the secret, and anything that can verify can now mint valid tokens.
RS256 (and every other asymmetric algorithm) splits the roles: the issuer signs with a private key it never shares, and any number of services verify with the public key — typically fetched from a JWKS endpoint. That's why OIDC providers sign with asymmetric algorithms, and why multi-service architectures should too. The split also removes the blast radius of the classic RS256-to-HS256 confusion attack, where a verifier is tricked into using a public key as an HMAC secret.
Which algorithm should you choose?
- Single service, issues and verifies its own tokens: HS256 with a long random secret (32+ bytes from a CSPRNG — never a password).
- Anything with more than one verifier: asymmetric. EdDSA or ES256 for new systems — smaller tokens, faster verification; RS256 when a library or provider in the chain doesn't support them yet (interoperability is its only remaining advantage).
- Already on RS256: fine — but pin your verifier to that exact family and rotate to PS256/EdDSA when the ecosystem allows.
Never trust the alg header
The token's own header says how it claims to be signed — an attacker controls it. Two non-negotiable verifier rules: alg:none (any capitalization) never verifies, and the accepted algorithm family must be derived from the key you configured, not read from the token. This is exactly how the TokenPrism debugger verifies — the key determines the family, and a mismatch is reported as an attack signal, not silently accepted.
Generate a key pair for any of these algorithms in your browser, then sign and verify a test token to see the differences hands-on.