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.

AlgorithmFamilyKey materialNotes
HS256 / HS384 / HS512HMAC (symmetric)One shared secretWhoever can verify can also forge
RS256 / RS384 / RS512RSA PKCS#1 v1.5Private + public keyThe de-facto default of every identity provider
PS256 / PS384 / PS512RSA-PSSPrivate + public keyModern RSA padding; prefer over RS* where supported
ES256 / ES384 / ES512ECDSAEC private + public keyFar smaller keys and signatures than RSA
EdDSA (Ed25519)Edwards-curvePrivate + public keyFast, 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.