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.

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.