Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Sign HS256 JWT

Paste a header and payload, provide a secret, copy the signed JWT. Useful for fixture generation in tests. Don't sign production tokens here.

 
 
⚠ Use this for testing only. Don't sign production tokens with secrets you've pasted into a web tool.
 
🔒 100% client-side · uses your browser's Web Crypto API · no upload

What this tool does

Given:

  1. A header object (alg is forced to HS256)
  2. A payload object (your claims)
  3. A secret (UTF-8 string)

The tool produces a JWT in the form base64url(header).base64url(payload).base64url(HMAC-SHA256(...)). The signature is computed via the browser's Web Crypto API.

Not sure whether HS256 is right for you? Read HS256 vs RS256.

When to use the Sign tab

When NOT to use the Sign tab

What "alg=HS256 is forced" means

Even if you change "alg" in the header textarea, the tool overwrites it to "HS256" before signing. This is a safety measure: it prevents you from accidentally generating a "none" token (which would bypass verification on misconfigured servers) or a header that doesn't match the actual signing operation.

Common claim shapes

Standard auth token

{
  "iss": "https://auth.example.com",
  "sub": "user-12345",
  "aud": "api.example.com",
  "exp": 1735689600,
  "iat": 1735603200,
  "jti": "0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b"
}

Refresh token

{
  "iss": "https://auth.example.com",
  "sub": "user-12345",
  "exp": 1740960000,
  "iat": 1735603200,
  "type": "refresh"
}

OIDC ID token

{
  "iss": "https://accounts.google.com",
  "sub": "1234567890",
  "aud": "your-client-id",
  "exp": 1735689600,
  "iat": 1735603200,
  "email": "alice@example.com",
  "email_verified": true,
  "name": "Alice Example",
  "nonce": "abc123"
}

Programmatic equivalents

// Node.js with jose
import { SignJWT } from "jose";
const secretKey = new TextEncoder().encode(secret);
const jwt = await new SignJWT(payload)
  .setProtectedHeader({ alg: "HS256" })
  .sign(secretKey);

// Python with PyJWT
import jwt
token = jwt.encode(payload, secret, algorithm="HS256")

// Go with golang-jwt
import "github.com/golang-jwt/jwt/v5"
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, _ := token.SignedString([]byte(secret))

FAQ

Should I sign production JWTs with this tool?

No. Don't paste production secrets into web tools — even client-side ones. The Sign tab is for testing, fixtures, and learning. For real production signing, do it on a server you control with a library like jose (Node), PyJWT (Python), or github.com/golang-jwt/jwt (Go).

Why HS256 only?

HS256 is the simplest algorithm — single secret, no key import. RSA and ECDSA require generating or importing a key pair, which adds UI complexity. We may add RS256 / ES256 in a future update. For MVP, HS256 covers the test-fixture use case.

How long should my secret be?

For HS256, the secret should be at least 256 bits (32 bytes) of high-entropy data. A short string like secret is dangerous — easy to brute-force. In production, generate with crypto.randomBytes(32).toString('base64') or equivalent.

Why does the tool force alg=HS256?

Safety. If we let you set alg: none or anything else, you could accidentally generate a token that bypasses verification on a misconfigured server. Hard-pinning to HS256 keeps the tool predictable.