Quickstart
This page walks you through a full sign/verify round trip, then a full encrypt/decrypt round trip. You can finish this page in two minutes just by copy-pasting the examples.
Sign and verify a token
The simplest JWT is an HMAC-signed token — same key on both sides.
sign-verify.ts
import { sign, verify } from "unjwt/jws";
import { generateJWK } from "unjwt/jwk";
// 1. Generate a symmetric key (once, at app start)
const key = await generateJWK("HS256");
// 2. Sign a payload (repeat per token)
const token = await sign({ sub: "user_1", role: "admin" }, key, { expiresIn: "1h" });
console.log(token);
// eyJhbGciOiJIUzI1NiIsImtpZCI6IjRj...
// 3. Verify and read the payload
const { payload, protectedHeader } = await verify(token, key);
console.log(payload);
// { sub: "user_1", role: "admin", iat: 1736860800, exp: 1736864400 }
console.log(protectedHeader);
// { alg: "HS256", typ: "JWT", kid: "4c3d..." }
What happened:
generateJWK("HS256")returned a symmetric JWK (kty: "oct") with a freshkid.sign(...)read the algorithm fromkey.alg, addediat(issued at) andexp(expiration) automatically because you passedexpiresIn: "1h", and produced a compact JWS string of the form<header>.<payload>.<signature>.verify(...)decoded the header, checked the signature, and validated the JWT claims (exp,nbf,iat) using the current clock.
expiresIn accepts numbers (seconds) or strings like "30s", "10m", "2h", "7D", "1W", "3M", "1Y". Same format everywhere in unjwt.Encrypt and decrypt a token
When the payload itself is sensitive (contains a PII, a secret, anything the client shouldn't read), use encryption instead of signing.
encrypt-decrypt.ts
import { encrypt, decrypt } from "unjwt/jwe";
// 1. Encrypt with a password — unjwt uses PBES2 under the hood
const jwe = await encrypt({ creditCard: "4242...", balance: 10_000 }, "my-strong-password");
console.log(jwe);
// eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ci...
// 2. Decrypt with the same password
const { payload } = await decrypt(jwe, "my-strong-password");
console.log(payload);
// { creditCard: "4242...", balance: 10000 }
The payload is now unreadable without the password. A JWE token still looks like dotted base64 segments, but the third segment onwards is ciphertext — no amount of base64 decoding gets you the claims back.
Password-based encryption is the easiest way in, but PBKDF2 is intentionally slow (default 600,000 iterations per OWASP). For high-throughput workloads, use a symmetric or asymmetric key instead.
Where next?
You've now seen the two main workflows. Pick whichever matches what you're building:
- Confused about JWT vs JWS vs JWE vs JWK? → Core concepts.
- Building an API with login tokens? → JWS overview and the authentication-basics example.
- Need to encrypt data at rest or in transit? → JWE overview.
- Working with third-party tokens (OAuth, OIDC, JWKS endpoints)? → Verifying with a key lookup and JWK Sets.
- Building on H3, Nuxt, or Nitro? → H3 session adapters.