← All guides

How to Sign a JWT with HS256

HS256 (HMAC using SHA-256) signs a token with a single shared secret. Whoever issues the token and whoever verifies it must both have the same secret. That's the whole tradeoff versus RS256: simpler to set up (one string, not a keypair), but every service that needs to verify tokens also needs the ability to forge them, since verifying and signing use the same key.

What actually gets signed

Not the JSON. The signature covers the ASCII bytes of base64url(header) + "." + base64url(payload) — the literal encoded string, dot included. This matters because if you re-serialize the JSON with different key order or spacing after generating the signature, verification will fail even though the “meaning” of the payload hasn't changed. The signature is over bytes, not semantics.

signature = HMAC-SHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

The header matters more than people think

{ "alg": "HS256", "typ": "JWT" } is the standard header, but the alg field is attacker-controlled once the token leaves your server — someone can decode a token, change alg to none, strip the signature, and see if a lazy verifier accepts it. A correct verifier hardcodes which algorithm it expects and never reads alg from the incoming token to decide how to verify it.

Secret length

HMAC-SHA256 technically accepts a secret of any length, but a short secret (say, under 32 bytes) is brute-forceable offline once someone has a valid token to test guesses against. Use a random secret of at least 32 bytes — openssl rand -base64 32 if you're generating one for a real service. Something like "secret123" is fine for testing this tool locally and nowhere else.

Try the JWT Encoder