HS256 and RS256 solve JWT signing differently — symmetric vs asymmetric keys. Here's how to pick the right one for your architecture and avoid mixing them.
The core difference
**HS256** (HMAC-SHA256) is a **symmetric** algorithm — the same secret is used to sign and verify a token. **RS256** (RSA-SHA256) is **asymmetric** — a private key signs the token, and a separate public key verifies it. Everything else follows from that one fact.
When HS256 makes sense
- A single backend service issues and verifies its own tokens.
- You control every consumer of the token and can distribute the shared secret safely.
- You want simplicity: one secret, generated once, rotated on a schedule.
The catch: anyone who can verify a token can also forge one, because the verification key and the signing key are identical. If you ever hand that secret to a third party "just to verify tokens," you've also handed them the ability to mint tokens as any user.
When RS256 makes sense
- Multiple services or third parties need to **verify** tokens but should never be able to **issue** them.
- You're building an OAuth2/OIDC authorization server that publishes a public key set for resource servers to consume.
- You want to support key rotation without redistributing a secret to every consumer — you just publish a new public key.
With RS256, the private key stays on the authorization server. Resource servers only ever see the public key (often via a JWKS endpoint), so a compromised resource server can't forge tokens.
Performance and size
- RS256 signatures are larger than HS256, and RSA signing/verification is more CPU-intensive than HMAC.
- HS256 verification is essentially free; RS256 verification is still fast enough for most APIs, just not free.
- If you're issuing millions of tokens per second from a single service, this can matter. For most applications, it doesn't.
The security trap: algorithm confusion
A well-known class of vulnerability lets an attacker take a legitimate RS256 token, and if a server naively trusts the `alg` header, resubmit it as an HS256 token **signed with the public key** (which is, well, public) as the HMAC secret. If the verifier doesn't pin the expected algorithm, it can be tricked into accepting a forged token. Always configure your JWT library with an explicit allow-list of algorithms — never read `alg` from the token and dispatch dynamically.
A simple decision table
- **Single trusted backend, no third parties verify tokens** → HS256 is fine and simpler.
- **Multiple services, microservices, or public API verify tokens** → RS256 (or ES256, its elliptic-curve cousin).
- **You're building or integrating with OAuth2/OIDC** → RS256, because that's the ecosystem convention and JWKS support is expected.
ES256 as a third option
ES256 (ECDSA with P-256) gives you the asymmetric properties of RS256 with smaller keys and signatures and comparable security. It's increasingly the default for new systems, but library support is slightly less universal than RS256.
Checklist
- Never let the token's `alg` header decide which key to use for verification.
- Use HS256 only when the signer and every verifier are the same trust boundary.
- Use RS256 or ES256 when tokens cross trust boundaries or you need public verifiability.
- Rotate keys with a `kid` header regardless of which algorithm you choose.
- Explicitly configure your verifier's allowed algorithm list — don't leave it open-ended.