HS256: HMAC-SHA256 JWTs

HS256 signs a JWT with HMAC using SHA-256 and a shared secret. The same secret signs and verifies, so it is ideal when one service both issues and checks its own tokens.

How HS256 works

signature = HMAC-SHA256(secret, b64url(header) + "." + b64url(payload))   // 32 bytes

Verifying recomputes the same HMAC with the same secret and compares. Because signing and verifying use the same key, anyone who can verify an HS256 token can also create one.

How long must the secret be?

RFC 7518 §3.2 requires a key at least as long as the hash output: 256 bits (32 bytes) for HS256. It should be random — not a word, phrase or password. An attacker with a single token can test billions of candidate secrets per second offline, so "secret", "changeme" or your company name will be found quickly. JWTEncoder warns about short and predictable secrets without blocking you, since reproducing a development token is a legitimate need.

Mind the encoding: many frameworks store the secret Base64-encoded and decode it before use. A secret entered as UTF-8 text on one side and Base64-decoded on the other produces different key bytes and a signature mismatch.

When to use (and not use) HS256

HS256 is not insecure by itself — weak secrets and shared secrets spread too widely are.

Sign HS256 in code

import { SignJWT } from "jose";

const key = new Uint8Array(Buffer.from(process.env.JWT_SECRET, "base64url"));
const now = Math.floor(Date.now() / 1000);

const payload = {
  sub: "user-123",
  iat: now,
  exp: now + 3600,
};

const token = await new SignJWT(payload)
  .setProtectedHeader({
    alg: "HS256",
    typ: "JWT",
  })
  .sign(key);

console.log(token);

Generate a random 256-bit secret and sign a token in the browser — then copy code in seven languages.

Create an HS256 JWT

Read more about JWT signing.