Jwt Lab
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.
Ask AI about Jwt Lab
Powered by Claude ยท Grounded in docs
I know everything about Jwt Lab. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
โโโโโโ โโโโโโโโโโโโ โโโ โโโโโโ โโโโโโโ
โโโโโโ โโโโโโโโโโโโ โโโ โโโโโโโโโโโโโโโโ
โโโโโโ โโ โโโ โโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โโ โโโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโ โโโ โโโโโโโโโโโ โโโโโโโโโโโ
โโโโโโ โโโโโโโโ โโโ โโโโโโโโโโโ โโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
v0.1.0 ยท JWT toolkit for developers & AI agents
jwt-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-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 ali@example.com 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":"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 --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.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 = 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 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 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-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 ~/.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 infpath.
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
- 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
