Package Exports
- jwt-lab
Readme
██╗██╗ ██╗████████╗ ██╗ █████╗ ██████╗
██║██║ ██║╚══██╔══╝ ██║ ██╔══██╗██╔══██╗
██║██║ █╗ ██║ ██║█████╗██║ ███████║██████╔╝
██ ██║██║███╗██║ ██║╚════╝██║ ██╔══██║██╔══██╗
╚█████╔╝╚███╔███╔╝ ██║ ███████╗██║ ██║██████╔╝
╚════╝ ╚══╝╚══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═════╝
────────────────────────────────────────────────────────
v0.1.0 · JWT toolkit for developers & AI agentsjwt-lab
The JWT Swiss-Army Knife for Developers & AI Agents
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-labQuick 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 ali@example.com expires in 12h" --secret s
# Start MCP server for AI agents
jwt mcp serve --port 3000Library (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":"user@example.com"}' \
--secret my-secret \
--exp 1h \
--iss https://auth.myapp.com
# Natural language (no LLM — deterministic regex parser)
jwt encode "admin user user@example.com 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 --jsonOptions:
| 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> --jsonjwt 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:00ZOutput:
✅ Valid JWT
Algorithm: HS256
Subject: user1jwt 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 formatOutput:
╭───── 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 formatOutput:
🔍 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 ES256Built-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-keyMCP 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.0Endpoints
| 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/docsAuthentication
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.redactClaimsto 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 = 100The 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 ali@example.com expires in 1h',
new Date()
);
// → { ok: true, value: { exp: ..., sub: 'ali@example.com', 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 encodingTech 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` directivesBash
One-liner (current session only):
eval "$(jwt completions bash)"Persistent — add to ~/.bashrc:
echo 'eval "$(jwt completions bash)"' >> ~/.bashrc
source ~/.bashrcOr save to the system completions directory (recommended for shared machines):
jwt completions bash | sudo tee /etc/bash_completion.d/jwt > /dev/nullRequires
bash-completionpackage. Install withbrew install bash-completionon macOS orapt install bash-completionon Debian/Ubuntu.
Zsh
One-liner (current session only):
eval "$(jwt completions zsh)"Persistent — add to ~/.zshrc:
echo 'eval "$(jwt completions zsh)"' >> ~/.zshrc
source ~/.zshrcOr 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 ~/.zshrcoh-my-zsh users: Save to
~/.oh-my-zsh/completions/_jwt— it's already infpath.
Fish
Fish completions are discovered automatically from ~/.config/fish/completions/. Just save the script there:
jwt completions fish > ~/.config/fish/completions/jwt.fishCompletions 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-tagFor 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:mcpCI/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
- Create and push a tag:
git tag v1.0.0 && git push origin v1.0.0 - Create a GitHub Release from that tag (set it to Published, not Draft)
- 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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT © jwt-lab contributors
Built with ❤️ for developers and AI agents