jlnstack

JWT

Type-safe JSON Web Token creation and verification

JWT

Create and verify JSON Web Tokens with type-safe payloads and schema validation.

Basic Usage

import { createJWT } from "@jlnstack/crypto/jwt"

const jwt = createJWT({
  secret: process.env.JWT_SECRET!,
})

// Sign a payload
const token = await jwt.sign({ userId: "123", role: "admin" })

// Verify and get the payload
const payload = await jwt.verify(token)
console.log(payload.userId) // "123"

Schema Validation

Add a schema to validate payloads on both sign and verify:

import { createJWT } from "@jlnstack/crypto/jwt"
import { z } from "zod"

const userToken = createJWT({
  payload: z.object({
    userId: z.string(),
    role: z.enum(["admin", "user"]),
  }),
  secret: process.env.JWT_SECRET!,
})

// Type-safe - TypeScript knows the shape
const token = await userToken.sign({
  userId: "123",
  role: "admin",
})

// Payload is typed as { userId: string, role: "admin" | "user" }
const { userId, role } = await userToken.verify(token)

Algorithms

Supports HMAC algorithms:

const jwt = createJWT({
  secret: process.env.JWT_SECRET!,
  algorithm: "HS256", // default
})

// Also available: "HS384", "HS512"

Sign Options

Add standard JWT claims when signing:

const token = await jwt.sign(
  { userId: "123" },
  {
    expiresIn: 3600,        // Expires in 1 hour (seconds)
    notBefore: 0,           // Valid immediately
    issuer: "my-app",       // iss claim
    subject: "user-123",    // sub claim
    audience: "api",        // aud claim
    jwtId: "unique-id",     // jti claim
  }
)

Default Options

Set defaults that apply to all tokens:

const jwt = createJWT({
  secret: process.env.JWT_SECRET!,
  defaults: {
    expiresIn: 3600,
    issuer: "my-app",
  },
})

// All tokens will expire in 1 hour and have issuer set
const token = await jwt.sign({ userId: "123" })

Verification Options

Validate claims during verification:

const payload = await jwt.verify(token, {
  clockTolerance: 30,           // Allow 30 seconds clock skew
  issuer: "my-app",             // Require specific issuer
  audience: ["api", "web"],     // Require one of these audiences
})

Decode Without Verification

Decode a token without verifying the signature:

const { header, payload } = jwt.decode(token)

console.log(header.alg) // "HS256"
console.log(payload.userId) // "123"

Error Handling

try {
  const payload = await jwt.verify(token)
} catch (error) {
  if (error.message === "Token has expired") {
    // Handle expiration
  } else if (error.message === "Invalid signature") {
    // Handle tampering
  } else if (error.message === "Invalid issuer") {
    // Handle wrong issuer
  }
}

API Reference

createJWT(config)

Creates a JWT instance.

Config:

  • secret (string | Uint8Array) — The secret key for signing
  • payload (StandardSchema, optional) — Schema to validate payloads
  • algorithm ("HS256" | "HS384" | "HS512", optional) — Algorithm to use, defaults to "HS256"
  • defaults (SignOptions, optional) — Default options for signing

Returns: JWT instance with sign, verify, and decode methods.

jwt.sign(payload, options?)

Signs a payload and returns a JWT string.

jwt.verify(token, options?)

Verifies a token and returns the payload. Throws on invalid tokens.

jwt.decode(token)

Decodes a token without verification. Returns { header, payload }.

On this page