A fast, secure JWT (JSON Web Token) CLI tool for decoding and validating tokens. Think jwt.io but for your command line.
- Decode JWTs without requiring secrets - inspect headers and payloads instantly
- Validate signatures with cryptographic verification
- All major algorithms supported: HMAC (HS256/384/512), RSA (RS256/384/512, PS256/384/512), ECDSA (ES256/384), EdDSA
- Multiple input methods: CLI argument, file, or stdin
- Multiple output formats: Human-readable (colorful), structured table, or JSON
- Multiple key sources: Direct input, file, or environment variable
The easiest way to install Jetta on macOS or Linux is via Homebrew:
# Add the tap
brew tap sam-lane/tap
# Install Jetta
brew install jetta
# Upgrade to the latest version
brew upgrade jettaDownload the latest release for your platform from the releases page.
Available platforms:
- Linux (x86_64, ARM64)
- macOS (Intel, Apple Silicon)
- Windows (x86_64)
Note: Starting with v1.0.0, binary artifacts follow the naming convention:
jetta_{version}_{os}_{arch}.tar.gz(e.g.,jetta_1.0.0_linux_amd64.tar.gz)jetta_{version}_{os}_{arch}.zipfor Windows
# Example: Install on Linux/macOS (v1.0.0+)
curl -LO https://github.com/Sam-Lane/jetta/releases/latest/download/jetta_1.0.0_linux_amd64.tar.gz
tar -xzf jetta_1.0.0_linux_amd64.tar.gz
sudo mv jetta /usr/local/bin/Each release includes SHA256 checksums for verification.
git clone https://github.com/Sam-Lane/jetta.git
cd jetta
cargo install --path .cargo install jettaRun jetta without any arguments to enter interactive mode with a cool animation:
jettaThis will:
- Show an animated transformation from base64 JWT → random characters → JSON
- Prompt you to paste a JWT token
- Decode the token and display the results
Skip the animation with --no-animation:
jetta --no-animation# Decode from command line argument
jetta decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Decode from file
jetta decode --file token.txt
# Decode from stdin (use '-' for explicit stdin)
cat token.txt | jetta decode -
echo $TOKEN | jetta decode -
# JSON output for scripting
jetta decode --format json $TOKEN | jq '.payload.sub'# Validate with HMAC secret
jetta validate --secret "your-secret-key" $TOKEN
# Validate with secret from file
jetta validate --secret-file secret.txt $TOKEN
# Validate with RSA/ECDSA/EdDSA public key
jetta validate --public-key public-key.pem $TOKEN
# Use environment variable
export JETTA_SECRET="your-secret-key"
jetta validate $TOKENSimply run jetta with no commands to enter interactive mode:
# With animation
jetta
# Skip animation
jetta --no-animation
# Pipe from stdin (skips prompt)
echo $TOKEN | jetta --no-animationThe animation morphs from base64 JWT through colorful random characters to a compact JSON representation of the decoded token.
Decode a JWT token without validating the signature. Useful for inspecting token contents.
jetta decode <TOKEN> [OPTIONS]Arguments:
<TOKEN>- JWT token string (required, use-to read from stdin)
Options:
-f, --file <FILE>- Read token from file (takes precedence over TOKEN argument)-o, --format <FORMAT>- Output format:human(default),table, orjson
Examples:
# Basic decode with colorful output
jetta decode $TOKEN
# Decode with JSON output
jetta decode --format json $TOKEN
# Decode from file
jetta decode --file my-token.txt
# Decode from stdin
echo $TOKEN | jetta decode -
cat my-token.txt | jetta decode -Validate a JWT signature and decode it. Exits with code 1 if validation fails.
jetta validate <TOKEN> [OPTIONS]Arguments:
<TOKEN>- JWT token string (required, use-to read from stdin)
Options:
-s, --secret <SECRET>- Secret key for HMAC algorithms--secret-file <FILE>- Read secret from file-k, --public-key <FILE>- Public key file in PEM format (for RSA/ECDSA/EdDSA)-o, --format <FORMAT>- Output format:human(default),table, orjson-f, --file <FILE>- Read token from file (takes precedence over TOKEN argument)
Examples:
# Validate with HMAC secret
jetta validate --secret "my-secret" $TOKEN
# Validate with RSA public key
jetta validate --public-key rsa-public.pem $TOKEN
# Use environment variable
export JETTA_SECRET="my-secret"
jetta validate $TOKEN
# JSON output
jetta validate --secret "my-secret" --format json $TOKEN
# Validate from stdin
echo $TOKEN | jetta validate --secret "my-secret" -| Algorithm | Type | Secret/Key Format |
|---|---|---|
| HS256 | HMAC | String or file |
| HS384 | HMAC | String or file |
| HS512 | HMAC | String or file |
| RS256 | RSA | PEM public key |
| RS384 | RSA | PEM public key |
| RS512 | RSA | PEM public key |
| PS256 | RSA-PSS | PEM public key |
| PS384 | RSA-PSS | PEM public key |
| PS512 | RSA-PSS | PEM public key |
| ES256 | ECDSA | PEM public key (PKCS8) |
| ES384 | ECDSA | PEM public key (PKCS8) |
| EdDSA | EdDSA | PEM public key |
- HMAC (HS)*: Use raw secret string or text file
- RSA (RS, PS)**: Use PEM-encoded RSA public key
- ECDSA (ES)*: Use PEM-encoded EC public key in PKCS8 format
- To convert SEC1 to PKCS8:
openssl pkeyutl -pubin -in ec-sec1.pem -out ec-pkcs8.pem
- To convert SEC1 to PKCS8:
- EdDSA: Use PEM-encoded EdDSA public key
Colorful, formatted output perfect for interactive use:
=== JWT Header ===
Algorithm: HS256
Type: JWT
=== JWT Payload ===
Subject: 1234567890
Issued At: 1516239022 (2018-01-18 01:30:22 UTC)
Custom Claims:
name: John Doe
=== Token Analysis ===
Algorithm: HS256
Has Signature: YES
Structured table format with Unicode box-drawing characters:
┌───────────┬──────────────────────────────────────────────┐
│ Header │
├───────────┼──────────────────────────────────────────────┤
│ alg │ HS256 │
│ typ │ JWT │
├───────────┼──────────────────────────────────────────────┤
│ Payload │
├───────────┼──────────────────────────────────────────────┤
│ sub │ 1234567890 │
│ name │ John Doe │
│ iat │ 1516239022 (2018-01-18 01:30:22 UTC) │
├───────────┼──────────────────────────────────────────────┤
│ Signature │
├───────────┼──────────────────────────────────────────────┤
│ value │ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │
├───────────┼──────────────────────────────────────────────┤
│ Validation │
├───────────┼──────────────────────────────────────────────┤
│ status │ VALID │
│ algorithm │ HS256 │
└───────────┴──────────────────────────────────────────────┘
Features:
- Section headers (Header, Payload, Signature, Validation) centered and spanning both columns
- Timestamps shown in both Unix and human-readable formats
- Complex values (arrays, objects) displayed as compact JSON
- Long values automatically truncated to 100 characters with
...ellipsis
Machine-readable output for scripting and automation:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
},
"analysis": {
"algorithm": "HS256",
"has_signature": true,
"issued_at": 1516239022
}
}JETTA_SECRET- Default secret for HMAC validation (fallback if no --secret provided)
# Human-readable (default, colorful)
jetta decode $TOKEN
# Structured table
jetta decode --format table $TOKEN
# JSON for scripting
jetta decode --format json $TOKEN | jq -r '.payload.email'if jetta validate --secret "$SECRET" $TOKEN; then
echo "Token is valid!"
else
echo "Token validation failed!"
fiwhile read -r token; do
echo "Processing token..."
jetta decode "$token"
done < tokens.txtThis error occurs when you run jetta decode or jetta validate without providing a token.
Solutions:
- Provide a token as an argument:
jetta decode <your-token> - Use
-to read from stdin:echo $TOKEN | jetta decode - - Use
--fileto read from a file:jetta decode --file token.txt - For interactive use, just run
jettawithout any command
This occurs when using - for stdin but no input is provided.
Solutions:
- Ensure you're piping data:
echo $TOKEN | jetta decode - - Or use a file instead:
jetta decode --file token.txt
The provided token is not a valid JWT format (must be three base64 parts separated by dots).
Solutions:
- Verify the token is complete and properly copied
- Check for extra whitespace or newlines
- Ensure it follows the format:
header.payload.signature
#!/bin/bash
# check-token.sh
export JETTA_SECRET="$(cat .secret)"
jetta validate --format json "$1" | jq '{
valid: .valid,
subject: .token.payload.sub,
expires: .token.analysis.expires_at
}'- Rust 1.88.0 or later
- Cargo
# Debug build
cargo build
# Release build (optimized)
cargo build --release
# Run tests
cargo test
# Run integration tests
cargo test --test integration_testssrc/
├── main.rs # CLI entry point and argument parsing
├── decode.rs # JWT decoding logic
├── validate.rs # Signature validation
├── output.rs # Output formatting (human/JSON)
├── animation.rs # Welcome animation logic
└── types.rs # Shared data structures
# All tests
cargo test
# Unit tests only
cargo test --lib
# Integration tests only
cargo test --test integration_tests
# Specific test
cargo test test_decode_valid_tokenMIT License - see LICENSE file for details
Contributions welcome! Please open an issue or PR.
Releases are automated via GitHub Actions and GoReleaser:
- Update
CHANGELOG.mdwith changes for the new version - Update version in
Cargo.toml - Run quality checks:
cargo fmt --all && cargo clippy --all-targets --all-features -- -D warnings && cargo test - Commit changes:
git commit -am "chore: release v1.0.0" - Create and push a tag:
git tag v1.0.0 && git push origin v1.0.0 - GitHub Actions will automatically:
- Build binaries for all platforms using GoReleaser
- Generate SHA256 checksums
- Create a GitHub release with assets
- Extract release notes from CHANGELOG.md
- Update the Homebrew tap with the new cask version
This project uses GitHub Actions for continuous integration and releases:
-
CI Workflow (
ci.yml): Runs on every push and PR- Tests on Linux, macOS, and Windows
- Checks code formatting with rustfmt
- Lints code with clippy (warnings treated as errors with
-D warnings)
-
Release Workflow (
release.yml): Runs on version tags- Uses GoReleaser with native Rust builder
- Builds optimized binaries for 5 platforms via cargo-zigbuild
- Generates checksums for verification
- Creates GitHub releases automatically
- Updates Homebrew tap (sam-lane/tap) with new cask