Create signed JSON Web Tokens with HMAC-SHA256/384/512
{{ error }}
A JSON Web Token (JWT, RFC 7519) is a compact, signed container for JSON data. It has three Base64URL-encoded parts separated by dots: header (algorithm), payload (claims) and signature. JWTs are used for stateless API authentication (bearer tokens), OAuth/OpenID Connect and single sign-on.
HS256/384/512 are symmetric HMAC algorithms — sender and receiver share the same secret. Simple and fast, fitting for internal APIs. For public APIs, asymmetric RS256 or ES256 (public/private key) is preferred — both supported by Web Crypto API but with more complex key management. 'none' (unsigned) should never be used in production.
Building a JWT happens in three steps. First, header and payload are each serialized as JSON. Then each part is Base64URL-encoded (RFC 4648 §5) — the URL-safe flavour with -/_ instead of +//, without padding =. The two encoded strings produce the signing input as base64url(header) + "." + base64url(payload). Only this complete string is signed, not the raw JSON. That matters: two semantically identical but differently serialized headers (different key order, whitespace) yield different signatures — when comparing tokens, you must compare canonical forms, not parsed objects.
The HMAC family (HS256/384/512) uses a shared key and is symmetric — anyone who can sign can also verify. Practical for monolithic apps with a single backend, problematic as soon as multiple services need to verify, because the secret has to live in each one. RS256 (RSA-PKCS1-v1.5 + SHA-256, RFC 3447) and ES256 (ECDSA on P-256) are asymmetric: the issuer holds the private key, all verifiers only need the public key — usually distributed via a JWKS endpoint at /.well-known/jwks.json. RFC 8037 explicitly recommends EdDSA (Ed25519) for new systems: no padding tricks, much shorter signatures (~88 bytes Base64 vs. 256 bytes for 2048-bit RSA) and constant-time verification.
alg: none is permitted by the spec and has caused years of security incidents. RFC 8725 ("JWT Best Current Practices") is explicit: never accept alg: none. The verifier should enforce a strict algorithm whitelist, ideally one concrete algorithm per endpoint. The second practical recommendation: set typ to JWT, only use cty for nested JWS/JWE. Third: use kid once more than one key is in flight so rotation becomes possible. The output of this generator is exactly what you put into the Authorization header: Authorization: Bearer <token>.
Build a production-ready token that mainstream verifiers will accept.
{"alg":"HS256","typ":"JWT"} for simple setups or {"alg":"RS256","typ":"JWT","kid":"key-2024-01"} with key rotation.iss (who issued it), sub (user id), aud (which service may use the token), iat, exp (Unix seconds).roles, tenant_id, scope. No passwords, no PII — JWT is not encrypted.openssl rand -base64 32. Never "secret" or "changeme" in production.Authorization: Bearer eyJhbGci.... The receiver verifies with the same secret or the matching public key.These examples show payloads as they are generated by real production systems.
{"sub":"u_42","iat":1700000000,"exp":1700003600,"aud":"api.example.com","scope":"read:profile write:posts"} — valid for one hour.{"sub":"u_42","iat":...,"exp":...+2592000,"typ":"refresh","jti":"uuid-..."} — valid for 30 days, with jti for revocation.{"sub":"u_42","purpose":"reset","iat":...,"exp":...+900,"jti":"..."} — valid for 15 minutes, single-use via jti blacklist.{"iss":"billing-svc","sub":"billing-svc","aud":"users-svc","iat":...,"exp":...+60} — very short-lived, identifies the calling service.{"iss":"https://auth.example.com","sub":"user-42","aud":"client-id","email":"[email protected]","nonce":"...","iat":...,"exp":...} — identity proof for login.First point: the HS256 secret must be cryptographically random. A dictionary word cracks in seconds — generate with openssl rand -base64 32 or crypto.randomBytes(32). Second point: short exp windows. Access tokens should live at most 15 minutes, ID tokens at most one hour, refresh tokens may live longer but then need revocation. Third point: token size. Every byte lands in every HTTP header. Above 1 KB some proxies and load balancers reply with 431 Request Header Fields Too Large. Fourth point: this generator only offers alg: none for demos — never accept it in production. Fifth point: the cryptographic operations here run in the browser via the Web Crypto API; the secret never leaves your device but is held in JavaScript memory. Avoid this tool for production secrets on unprotected machines.
openssl rand -base64 32 or node -e "console.log(require('crypto').randomBytes(32).toString('base64'))". Minimum length: 256 bits (32 bytes) for HS256, 384 bits for HS384, 512 bits for HS512. Store it in a secret manager like Vault, AWS Secrets Manager or Doppler — never in a Git repo.iss, sub, aud, iat, exp. For revocation add jti. The OIDC spec additionally requires nonce for the authorization-code flow with implicit parts.exp be?header.encryptedKey.iv.ciphertext.tag. In practice few APIs need JWE — the more common setup is TLS (encrypts the entire transport) plus a signed but unencrypted JWS.crypto.subtle.sign). No request is sent to any server, the secret exists only in your RAM. You can load the page offline and it still works. Even so: only enter production secrets on a device you fully trust.