HS256 (HMAC-SHA256)

HS256 is the default symmetric JWT signing algorithm in most stacks: one shared secret signs and verifies every token using HMAC-SHA256 as registered in RFC 7518. You implement it when a single auth service or tightly coupled backend both issues and validates tokens, and you can store a high-entropy secret in environment variables or a secrets manager. It is fast, library support is universal, and operational setup is minimal compared to RSA or ECDSA key pairs. The trade-off is that every verifier must possess the signing secret, which expands your blast radius if one service leaks credentials or logs the key by mistake.

Last updated July 25, 2026

How It Works

When you sign with HS256, your library Base64URL-encodes a JSON header containing alg HS256 and typ JWT, Base64URL-encodes the claims payload, and joins both segments with a dot to form the signing input. It then computes HMAC-SHA256 over that string using your shared secret key material and appends the Base64URL-encoded digest as the third segment. Verification decodes the header and payload without trusting them yet, recomputes the HMAC with the same secret, and compares the result using a constant-time comparison; any mismatch, truncated signature, or unexpected algorithm in the header must reject the token. RFC 7519 defines the compact serialization; RFC 7518 registers HS256 specifically as HMAC using the SHA-256 hash function. Because signing and verification are symmetric, there is no public key or JWKS endpoint to publish—only secret generation, distribution, and rotation discipline matter. In production code, always pass an explicit algorithms allowlist to verify (for example algorithms: ['HS256']), validate standard claims like exp, nbf, iss, and aud, and never branch on unverified payload data. Load secrets from a vault or sealed environment variables rather than hard-coding values in repositories. Use /tools/jwt-secret-generator with the 256-bit preset to create production-grade material, then inject the same value into your issuer and every authorized verifier through your deployment pipeline.

When to Use

Choose HS256 for monoliths, backend-for-frontend layers, and internal APIs where one team controls both token issuance and verification. It fits replacing server-side sessions, service-to-service calls inside a private network, and prototypes that must ship quickly without operating JWKS infrastructure. When all legitimate consumers can receive the secret through a vault and you accept symmetric key distribution, HS256 is usually the lowest-friction correct choice for new internal auth.

When Not to Use

Avoid HS256 when untrusted third parties verify tokens, when many microservices must validate JWTs but must not hold the signing key, or when compliance mandates asymmetric signatures with auditable key separation. Do not use it if browser or mobile clients would need the secret, if you cannot rotate credentials without redeploying every consumer, or if a leaked verifier credential would let an attacker forge tokens network-wide. Those scenarios belong on RS256, ES256, or opaque server-side sessions instead.

Key Length Requirements

RFC 7518 requires key material at least as long as the HMAC hash output: for HS256 that means a minimum of 256 bits (32 bytes). Production services should use exactly 32 cryptographically random bytes—64 hexadecimal characters—not passphrases, UUIDs, or truncated passwords. NIST guidance treats 128-bit symmetric keys as a baseline; 256 bits provides comfortable margin against brute force. If you store the secret as Base64, expect 44 characters including padding for 32 raw bytes. Never reuse an HS256-sized secret for HS384 or HS512; each HMAC variant needs key length matching its hash size. Generate material with openssl rand -hex 32, your language CSPRNG, or the jwt-secret-generator tool at 256-bit preset, then keep dev, staging, and production secrets distinct.

Security Pitfalls

  • Using short or human-readable secrets such as my-super-secret-key, which fall to offline guessing regardless of algorithm strength.
  • Omitting an explicit algorithms allowlist in jwt.verify, which enables alg:none or algorithm-confusion attacks when parsers accept unexpected values.
  • Logging Authorization headers, error traces, or environment dumps that include JWT_SECRET, exposing the signing key to anyone with log access.
  • Sharing one production secret across every microservice and contractor environment so a single leak grants universal token forgery capability.
  • Trusting payload claims before signature verification completes, or skipping exp, iss, and aud checks because the token decoded successfully.

Code Examples

Node.js — sign and verify with jsonwebtoken

const jwt = require('jsonwebtoken');
const crypto = require('crypto');

// 256-bit (32-byte) secret for HS256
const secret = crypto.randomBytes(32);

const token = jwt.sign(
  { sub: 'user_123', role: 'admin' },
  secret,
  { algorithm: 'HS256', expiresIn: '1h', issuer: 'https://auth.example.com' }
);

const payload = jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'https://auth.example.com',
});

console.log(payload.sub); // user_123

Python — sign and verify with PyJWT

import secrets
import jwt

secret = secrets.token_bytes(32)  # 256-bit key material

token = jwt.encode(
    {"sub": "user_123", "role": "admin"},
    secret,
    algorithm="HS256",
)

payload = jwt.decode(
    token,
    secret,
    algorithms=["HS256"],
    issuer="https://auth.example.com",
    options={"require": ["exp", "sub"]},
)

print(payload["sub"])

Generate Keys

Recommended: 256-bit

Open Jwt Secret Generator

Related Comparisons

Related Terms

Related Guides

Frequently Asked Questions

How do I generate a production-ready HS256 secret?

Use 32 cryptographically random bytes and encode them as hex or Base64, or run openssl rand -hex 32 locally. The jwt-secret-generator tool with the 256-bit preset produces correctly sized material you can paste into environment variables. Never derive secrets from passwords, repository names, or timestamps—predictable input collapses HMAC security regardless of algorithm choice.

Can I use the same HS256 secret in development and production?

You should not. Separate secrets per environment limits blast radius when a developer laptop, CI log, or staging dump leaks credentials. Store production values only in a secrets manager and inject them at runtime; rotate immediately if a non-production secret ever matched production.

Why must I pass algorithms: ['HS256'] when verifying?

Explicit allowlists prevent algorithm confusion and alg:none bypasses where attackers swap the header algorithm or strip signatures while parsers still accept payloads. Libraries that default to trusting the header alg field have historically enabled critical vulnerabilities. Pinning HS256 at verify time ensures only HMAC-SHA256 tokens validate even if an attacker presents RS256 or none in the header.

When should I migrate from HS256 to RS256?

Migrate when multiple independent services verify tokens but only one should sign them, or when third parties validate JWTs without receiving your signing secret. RS256 lets you publish a public key or JWKS endpoint while keeping the private key on the issuer. HS256 remains appropriate for single-service or tightly coupled deployments where symmetric distribution is already solved.

Looking for a short definition?

Glossary: HS256 (HMAC-SHA256)