npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

jwt-lab

v0.0.9

Published

jwt-lab – A fast, secure, beautiful JWT CLI tool and MCP server for developers & AI agents. Encode, decode, verify, inspect, audit, and generate keys for JSON Web Tokens.

Readme


     ██╗██╗    ██╗████████╗   ██╗      █████╗ ██████╗ 
     ██║██║    ██║╚══██╔══╝   ██║     ██╔══██╗██╔══██╗
     ██║██║ █╗ ██║   ██║█████╗██║     ███████║██████╔╝
██   ██║██║███╗██║   ██║╚════╝██║     ██╔══██║██╔══██╗
╚█████╔╝╚███╔███╔╝   ██║      ███████╗██║  ██║██████╔╝
 ╚════╝  ╚══╝╚══╝    ╚═╝      ╚══════╝╚═╝  ╚═╝╚═════╝
────────────────────────────────────────────────────────
v0.1.0 · JWT toolkit for developers & AI agents

jwt-lab

The JWT Swiss-Army Knife for Developers & AI Agents

npm version license node TypeScript CI CodeQL tests MCP

Encode · Decode · Verify · Inspect · Explain · Keygen · MCP Server

A fast, secure, beautiful, and AI-agent-ready command-line tool and TypeScript/JavaScript library for working with JSON Web Tokens (JWTs), plus a full Model Context Protocol (MCP) HTTP/JSON server.

Installation · Quick Start · Commands · MCP Server · Configuration · API Reference


Why jwt-lab?

| Feature | jwt-lab | jwt.io | Other CLIs | |---------|---------|--------|------------| | 🔐 Security linting & audit | ✅ 6 built-in rules | ❌ | ❌ | | 🤖 AI-native MCP server | ✅ Full HTTP/JSON API | ❌ | ❌ | | � Programmatic TypeScript API | ✅ ESM + CJS, Result<T,E> | ❌ | ❌ | | 🔍 One-call token inspection | ✅ inspectToken() | ❌ | ❌ | | �🗣️ Natural language encoding | ✅ "admin token expires in 1h" | ❌ | ❌ | | ⏰ Time travel (--fake-time) | ✅ Deterministic testing | ❌ | ❌ | | 📋 Config as code (.jwt-cli.toml) | ✅ Profiles, defaults, keys | ❌ | ❌ | | 🎨 Premium terminal UX | ✅ Colors, boxes, tables | ❌ | Partial | | 🔑 Key generation (RSA/EC/Ed25519) | ✅ JWK + PEM output | ❌ | Partial | | 📦 Dual ESM + CJS output | ✅ | N/A | ❌ | | 🧪 Strict TypeScript, zero any | ✅ | N/A | ❌ |


Installation

# Global install (recommended for CLI)
npm install -g jwt-lab

# Or use with npx
npx jwt-lab --help

# Add to a project (as a library)
npm install jwt-lab

Quick Start

CLI

# Encode a JWT with HMAC secret
jwt encode '{"sub":"user1","role":"admin"}' --secret my-secret --exp 1h

# Decode without verification
jwt decode eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMSJ9.xxx

# Verify signature + claims
jwt verify <token> --secret my-secret

# Security audit (no keys needed)
jwt explain <token>

# Inspect with full breakdown
jwt inspect <token> --secret my-secret

# Generate key pairs
jwt keygen ec --pem --out-dir ./keys

# Natural language encoding
jwt encode "admin token for user [email protected] expires in 12h" --secret s

# Start MCP server for AI agents
jwt mcp serve --port 3000

Library (TypeScript / JavaScript)

import { inspectToken, encodeToken, verifyToken, generateKeyPair } from 'jwt-lab';

// One-call inspection: decode + verify + lint
const result = await inspectToken({
  token: 'eyJ...',
  secret: 'my-secret',
});
if (result.ok) {
  console.log(result.value.status);     // "valid" | "expired" | "not_yet_valid" | "unverified"
  console.log(result.value.algorithm);   // "HS256"
  console.log(result.value.lintFindings); // security findings
}

// Or use individual functions for fine-grained control
const token = await encodeToken({
  payload: { sub: 'user1', role: 'admin' },
  secret: 'my-secret',
  alg: 'HS256',
});

const verified = await verifyToken({
  token: token.value,
  secret: 'my-secret',
  // alg auto-detected from token header
});

// Generate keys
const keys = await generateKeyPair({ type: 'ec', format: 'jwk' });

Commands

jwt encode

Encode a JWT from JSON or natural language.

# JSON payload
jwt encode '{"sub":"user1","role":"admin","email":"[email protected]"}' \
  --secret my-secret \
  --exp 1h \
  --iss https://auth.myapp.com

# Natural language (no LLM — deterministic regex parser)
jwt encode "admin user [email protected] expires in 30m" --secret s

# With asymmetric key
jwt encode '{"sub":"svc"}' --key ./private.pem --alg ES256

# With profile from config
jwt encode '{"sub":"user1"}' --secret s --profile access_token

# Copy to clipboard
jwt encode '{"sub":"user1"}' --secret s --exp 1h --copy

# JSON output
jwt encode '{"sub":"user1"}' --secret s --json

Options:

| Flag | Description | |------|-------------| | --secret <string> | HMAC secret (HS256/384/512) | | --key <path> | PEM or JWK private key file | | --alg <algorithm> | Signing algorithm | | --exp <duration> | Expiration (e.g., 1h, 30m, 7d) | | --iss <string> | Issuer claim | | --sub <string> | Subject claim | | --aud <string> | Audience claim | | --kid <string> | Key ID in header | | --jti | Generate random UUID as JTI | | --header <json> | Additional header fields | | --profile <name> | Use named profile from config | | --copy | Copy token to clipboard | | --json | Output as JSON |


jwt decode

Decode a JWT without verification.

jwt decode <token>

# From stdin
echo "<token>" | jwt decode -

# Batch mode
cat tokens.txt | jwt decode - --batch

# JSON output
jwt decode <token> --json

jwt verify

Full signature verification and claims validation.

# HMAC
jwt verify <token> --secret my-secret

# Asymmetric key
jwt verify <token> --key ./public.pem --alg ES256

# JWKS endpoint
jwt verify <token> --jwks https://auth.example.com/.well-known/jwks.json

# Required claims
jwt verify <token> --secret s --require sub,iss,exp

# Clock skew tolerance
jwt verify <token> --secret s --leeway 30

# Time travel for testing
jwt verify <token> --secret s --fake-time 2024-01-01T00:00:00Z

Output:

✅ Valid JWT
Algorithm: HS256
Subject:   user1

jwt inspect

High-level token breakdown with status, metadata, and security posture.

jwt inspect <token>
jwt inspect <token> --secret my-secret  # with verification
jwt inspect <token> --json              # machine-readable
jwt inspect <token> --table             # table format

Output:

╭───── Token Inspection ──────╮
│                              │
│  Status: ✅ valid            │
│  Algorithm: HS256            │
│  Subject:   user1            │
│  Issuer:    auth.example.com │
│  Expires in: 59m 30s         │
│                              │
│  Lint Findings:              │
│  ⚠️ [pii-claims] Payload ... │
│                              │
╰──────────────────────────────╯

jwt explain

Static security audit — no keys required.

jwt explain <token>
jwt explain <token> --json    # for CI pipelines
jwt explain <token> --table   # table format

Output:

🔍 JWT Security Audit

❌ [none-algorithm] Token uses the "none" algorithm
  → Replace "none" with a secure algorithm such as RS256 or ES256

⚠️ [pii-claims] Payload contains claims that may hold PII: email
  → Avoid embedding PII directly in JWT payloads

ℹ️ [hmac-preferred-asymmetric] Token uses HMAC algorithm (HS256)
  → Consider using an asymmetric algorithm such as RS256 or ES256

Built-in security rules:

| Rule ID | Severity | What it checks | |---------|----------|----------------| | none-algorithm | 🔴 error | Algorithm is "none" | | missing-exp | 🟡 warn | Token has no expiration | | long-lived-token | 🟡 warn | Lifetime > 24 hours | | pii-claims | 🟡 warn | Claims containing PII patterns | | missing-nbf-long-lived | 🔵 info | Long-lived token without nbf | | hmac-preferred-asymmetric | 🔵 info | HMAC where asymmetric is preferred |


jwt keygen

Generate cryptographic key pairs.

# EC key pair (default P-256)
jwt keygen ec

# RSA key pair
jwt keygen rsa --bits 4096

# Ed25519
jwt keygen ed25519

# PEM output to files
jwt keygen ec --pem --out-dir ./keys

# JWK with key ID
jwt keygen rsa --jwk --kid my-production-key

MCP Server

jwt-lab includes a full Model Context Protocol HTTP/JSON server for AI agents and programmatic access.

Start the server

jwt mcp serve --port 3000 --host 0.0.0.0

Endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /encode | Encode a JWT | | POST | /decode | Decode a JWT | | POST | /verify | Verify a JWT | | POST | /inspect | Inspect a JWT | | POST | /keygen | Generate key pair | | POST | /explain | Security audit | | GET | /docs | OpenAPI 3.1 spec | | GET | /health | Health check |

Examples with curl

# Encode
curl -X POST http://localhost:3000/encode \
  -H "Content-Type: application/json" \
  -d '{"payload":{"sub":"user1"},"secret":"my-secret","alg":"HS256","exp":"1h"}'

# Decode
curl -X POST http://localhost:3000/decode \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJhbGciOiJIUzI1NiJ9..."}'

# Verify
curl -X POST http://localhost:3000/verify \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJ...","secret":"my-secret"}'

# Explain (security audit)
curl -X POST http://localhost:3000/explain \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJ..."}'

# Generate key pair
curl -X POST http://localhost:3000/keygen \
  -H "Content-Type: application/json" \
  -d '{"type":"ec","format":"jwk"}'

# OpenAPI docs
curl http://localhost:3000/docs

Authentication

Set the MCP_API_KEY environment variable to enable Bearer token authentication:

MCP_API_KEY=your-secret-key jwt mcp serve

# Then include the key in requests:
curl -X POST http://localhost:3000/encode \
  -H "Authorization: Bearer your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"sub":"user1"},"secret":"s","alg":"HS256"}'

Security Features

  • Token redaction: Full tokens are never logged; truncated to 20 chars
  • Claim redaction: Configure mcp.redactClaims to hide sensitive claims in responses
  • Rate limiting: Sliding window per IP (configurable)
  • CORS: Configurable allowed origins
  • Input validation: All requests validated with Zod schemas

Configuration

Create a .jwt-cli.toml in your project root:

[defaults]
iss = "https://auth.myapp.com/"
aud = "myapp-api"
alg = "ES256"

[profiles.access_token]
ttl    = "15m"
scopes = ["read", "write"]

[profiles.service_token]
ttl = "1h"
aud = "internal-service"

[lint]
piiClaimPatterns = ["email", "phone", "ssn"]

[lint.severityOverrides]
"missing-exp" = "error"

[mcp]
port = 3000
redactClaims = ["email", "phone"]

[mcp.rateLimit]
windowSeconds = 60
maxRequests   = 100

The CLI auto-discovers .jwt-cli.toml by walking upward from the current directory. Use --config <path> to specify a custom path.

Priority: CLI flags > config file > built-in defaults


Global Flags

| Flag | Description | |------|-------------| | --help | Show help | | --version | Show version | | --fake-time <iso8601> | Override system clock | | --config <path> | Path to config file | | --json | Machine-readable JSON output |


API Reference

Core Library

jwt-lab's core is a pure, I/O-free TypeScript library (except for OIDC/JWKS which makes HTTP requests). All functions return Result<T, E> types — no exceptions thrown.

import {
  // High-level
  inspectToken,        // decode + verify + lint in one call

  // Low-level building blocks
  encodeToken,         // sign a JWT
  decodeToken,         // decode without verification
  verifyToken,         // verify signature + claims
  lintToken,           // security audit (no keys needed)
  generateKeyPair,     // RSA, EC, Ed25519 key pairs
  parseDuration,       // "1h30m" → 5400 seconds
  parseNaturalLanguagePayload,  // NLP → JWT payload

  // OIDC / JWKS
  resolveOidcJwksUri,  // fetch JWKS URI from OIDC discovery
  buildDiscoveryUrl,   // issuer → discovery URL
} from 'jwt-lab';

inspectToken(opts) — High-Level API

The inspectToken function is the library equivalent of jwt inspect and jwt verify --oidc-discovery. It composes decode → OIDC discovery → verify → lint in a single call:

import { inspectToken } from 'jwt-lab';

// With HMAC secret
const result = await inspectToken({
  token: 'eyJ...',
  secret: 'my-secret',
});

// With OIDC discovery (auto-resolves JWKS)
const result = await inspectToken({
  token: 'eyJ...',
  oidcDiscoveryUrl: 'https://accounts.google.com',
});

// With asymmetric key (auto-detects algorithm from token header)
const result = await inspectToken({
  token: 'eyJ...',
  publicKeyPem: '-----BEGIN PUBLIC KEY-----\n...',
});

// Decode + lint only (no key → status = "unverified")
const result = await inspectToken({ token: 'eyJ...' });

if (result.ok) {
  const { status, algorithm, issuer, subject, expiresAt,
          customClaims, verificationResult, lintFindings } = result.value;
  // status: "valid" | "expired" | "not_yet_valid" | "unverified"
}

InspectTokenOptions:

| Option | Type | Description | |--------|------|-------------| | token | string | JWT string (required) | | secret | string | HMAC secret | | publicKeyPem | string | PEM-encoded public key | | publicKeyJwk | object | JWK public key | | jwksUri | string | Remote JWKS endpoint | | oidcDiscoveryUrl | string | OIDC discovery URL (auto-resolves JWKS) | | alg | SupportedAlgorithm | Expected algorithm (auto-detected if omitted) | | requiredClaims | string[] | Claims that must be present | | leewaySeconds | number | Clock skew tolerance | | lintConfig | LintConfig | Lint rule overrides | | now | Date | Clock override for testing |

InspectResult:

| Field | Type | Description | |-------|------|-------------| | status | "valid" \| "expired" \| "not_yet_valid" \| "unverified" | Overall token status | | algorithm | string | Algorithm from the header | | kid | string? | Key ID from the header | | issuer | string? | iss claim | | subject | string? | sub claim | | audience | string \| string[]? | aud claim | | issuedAt | Date? | iat as Date | | expiresAt | Date? | exp as Date | | notBefore | Date? | nbf as Date | | timeUntilExpiry | number? | Seconds until expiry (negative = expired) | | customClaims | Record<string, unknown> | Non-standard claims | | verificationResult | Result<true, VerifyError>? | Signature check result | | lintFindings | LintFinding[] | Security audit findings |

Low-Level Functions

// Encode a JWT
const token = await encodeToken({
  payload: { sub: 'user1', role: 'admin' },
  secret: 'my-secret',
  alg: 'HS256',
});

// Decode without verification
const decoded = decodeToken('eyJ...');
// → { header, payload, signaturePresent }

// Verify signature + claims
const verified = await verifyToken({
  token: 'eyJ...',
  secret: 'my-secret',
  // alg auto-detected from header if omitted
});

// Security audit (no keys needed)
const decoded = decodeToken('eyJ...');
const findings = lintToken(decoded.value, {
  piiClaimPatterns: ['email', 'phone'],
});

// Generate key pairs
const keys = await generateKeyPair({
  type: 'ec',       // 'rsa' | 'ec' | 'ed25519'
  format: 'jwk',    // 'jwk' | 'pem'
  kid: 'my-key-id',
});

// Parse durations
const seconds = parseDuration('1h30m');
// → { ok: true, value: 5400 }

// Natural language → payload
const payload = parseNaturalLanguagePayload(
  'admin token for user [email protected] expires in 1h',
  new Date()
);
// → { ok: true, value: { exp: ..., sub: '[email protected]', email: '...', role: 'admin' } }

Algorithm Auto-Detection

verifyToken and inspectToken auto-detect the signing algorithm from the JWT header when alg is not explicitly provided. This means you can verify tokens without knowing the algorithm in advance:

// No need to specify alg — auto-detected from the token header
const result = await verifyToken({
  token: ecSignedJwt,
  publicKeyPem: ecPublicKey,
});

See src/core/ for full API documentation with TSDoc comments.

Examples

The examples/api-usage/ directory contains runnable TypeScript examples:

| Script | Description | |--------|-------------| | npm start | All core functions: encode, decode, verify, lint, keygen, inspectToken, NLP | | npm run inspect-local | inspectToken with local keys (HMAC, EC, expired, no-key) | | npm run verify-asymmetric | All asymmetric algorithms + auto-detection (EC, RSA, Ed25519) | | npm run nlp-encode | Natural language → JWT payload → encode → decode round-trip | | npm run oidc-inspect | OIDC token inspection with a single inspectToken call |

cd examples/api-usage
npm install
npm start                   # run main examples
npm run inspect-local       # inspectToken with local keys
npm run verify-asymmetric   # EC, RSA, Ed25519 verification
npm run nlp-encode          # natural language encoding

Tech Stack

| Category | Choice | |----------|--------| | Language | TypeScript 6 (strict mode, zero any) | | Runtime | Node.js ≥ 22 | | JWT | jose v6 | | CLI | commander v14 | | Validation | zod v4 | | HTTP | hono + @hono/node-server | | Build | tsup (dual ESM + CJS) | | Tests | vitest v4 | | Terminal | picocolors, boxen, ora, cli-table3 |


Shell Completions

jwt-lab ships with built-in tab-completion scripts for Bash, Zsh, and Fish. The completions are aware of every subcommand and flag — pressing Tab surfaces commands, options, algorithm names, and file paths in context.

How it works

The jwt completions <shell> command prints a shell-specific completion script to stdout. You either eval it at shell startup or write it to a file that your shell auto-loads. No third-party tools are required.

jwt completions bash   →  prints a Bash completion function + `complete -F` binding
jwt completions zsh    →  prints a Zsh `_jwt` compdef function
jwt completions fish   →  prints Fish `complete` directives

Bash

One-liner (current session only):

eval "$(jwt completions bash)"

Persistent — add to ~/.bashrc:

echo 'eval "$(jwt completions bash)"' >> ~/.bashrc
source ~/.bashrc

Or save to the system completions directory (recommended for shared machines):

jwt completions bash | sudo tee /etc/bash_completion.d/jwt > /dev/null

Requires bash-completion package. Install with brew install bash-completion on macOS or apt install bash-completion on Debian/Ubuntu.

Zsh

One-liner (current session only):

eval "$(jwt completions zsh)"

Persistent — add to ~/.zshrc:

echo 'eval "$(jwt completions zsh)"' >> ~/.zshrc
source ~/.zshrc

Or save to a $fpath directory (the clean approach):

# Pick any directory already in your fpath, or create one
mkdir -p ~/.zsh/completions
jwt completions zsh > ~/.zsh/completions/_jwt

# Make sure the directory is in fpath — add to ~/.zshrc if not already there:
echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
source ~/.zshrc

oh-my-zsh users: Save to ~/.oh-my-zsh/completions/_jwt — it's already in fpath.

Fish

Fish completions are discovered automatically from ~/.config/fish/completions/. Just save the script there:

jwt completions fish > ~/.config/fish/completions/jwt.fish

Completions take effect immediately — no source or restart needed.

What gets completed

| Context | Completions offered | |---------|---------------------| | jwt <Tab> | All subcommands with descriptions | | jwt encode <Tab> | --secret, --key, --alg, --exp, --iss, --json, … | | jwt verify <Tab> | --secret, --key, --jwks, --oidc-discovery, --alg, --require, --leeway, … | | jwt keygen <Tab> | Algorithm types: RS256 RS384 RS512 ES256 ES384 ES512 EdDSA PS256 PS384 PS512 | | jwt --alg <Tab> | Full algorithm list | | --key <Tab> | File path completion (all shells) | | --config <Tab> | File path completion (all shells) | | jwt completions <Tab> | bash zsh fish |


CI/CD & Publishing

Every push and pull request to main runs the full pipeline:

| Job | Steps | |-----|-------| | Test & Build | lint → type-check → tests → build → CLI smoke test (Node 22 & 24) | | Security Audit | npm audit at moderate severity | | CodeQL | Static analysis for JavaScript/TypeScript (separate scheduled workflow) | | Publish | Runs only on v* tag push → bumps version → builds → publishes to npm with provenance |

Publishing a release

# 1. Bump version
npm version 1.0.0 --no-git-tag-version
git add package.json && git commit -m "chore: bump version to 1.0.0"
git push origin main

# 2. Create a GPG-signed annotated tag
git tag -s v1.0.0 -m "Release v1.0.0"
git tag -v v1.0.0  # verify signature

# 3. Push tag — triggers the publish workflow
git push origin v1.0.0

# 4. Create a signed GitHub Release
gh release create v1.0.0 --title "v1.0.0" --notes "Release notes here" --verify-tag

For a prerelease (e.g. v1.1.0-beta.1), the package is published with the beta dist-tag automatically.

The workflow uses npm publish --provenance, which attaches a cryptographic SLSA Level 2 attestation proving the package was built from this exact commit.

Required repository secret

| Secret | Description | |--------|-------------| | NPM_TOKEN | npm Automation token with publish access — add at Settings → Secrets → Actions |

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Type check
npm run type-check

# Lint
npm run lint

# Start MCP server (dev)
npm run dev:mcp

CI/CD & Publishing

Every push and pull request to main runs the full pipeline:

| Job | Steps | |-----|-------| | Test & Build | lint → type-check → tests → build → CLI smoke test (Node 22 & 24) | | Security Audit | npm audit at moderate severity | | CodeQL | Static analysis for JavaScript/TypeScript (separate scheduled workflow) | | Publish | Runs only on GitHub Release publish → bumps version → builds → publishes to npm |

Publishing a release

  1. Create and push a tag: git tag v1.0.0 && git push origin v1.0.0
  2. Create a GitHub Release from that tag (set it to Published, not Draft)
  3. The pipeline auto-bumps package.json, builds, and publishes to npm

For a prerelease (e.g. v1.1.0-beta.1), the package is published with the beta dist-tag automatically.

Required repository secret

| Secret | Description | |--------|-------------| | NPM_TOKEN | npm automation token with publish access — add in Settings → Secrets → Actions |

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © jwt-lab contributors


Built with ❤️ for developers and AI agents

Report Bug · Request Feature · Discussions