Architecture¶
This document describes the high-level architecture of jwt-term, a Rust CLI tool for JWT inspection, validation, and manipulation.
Overview¶
jwt-term follows a thin CLI, thick library architecture: the CLI layer is a minimal shell that parses arguments and delegates to a core library containing all business logic. This separation keeps the core logic testable without involving CLI concerns.
Design Principles¶
- Thin CLI, thick library --
main.rsandcli.rshandle only argument parsing and output dispatch. All JWT logic lives insrc/core/. - Separation of concerns -- Display formatting in
src/display/, business logic insrc/core/, CLI wiring insrc/commands/. - Security by default -- Sensitive data is never logged. Secrets are zeroized after use. Unsafe code is forbidden at the compiler level.
- Explicit error handling -- Domain errors are strongly typed with
thiserror.anyhowis used only at the CLI boundary. - Offline-first -- Network access is only triggered by an explicit
--jwks-urlflag.
Project Structure¶
jwt-term/
├── src/
│ ├── main.rs # Entry point, #![forbid(unsafe_code)]
│ ├── cli.rs # Clap derive definitions
│ ├── error.rs # Domain error types (thiserror)
│ ├── commands/ # Subcommand handlers
│ │ ├── mod.rs
│ │ ├── decode.rs # Decode subcommand
│ │ └── verify.rs # Verify subcommand
│ ├── core/ # Business logic (no CLI dependencies)
│ │ ├── mod.rs
│ │ ├── decoder.rs # JWT splitting, base64url decoding
│ │ ├── validator.rs # Signature validation
│ │ ├── jwks.rs # JWKS fetching and key matching
│ │ └── time_travel.rs # Time expression parsing
│ └── display/ # Terminal output formatting
│ ├── mod.rs
│ ├── json_printer.rs # Colorized JSON output
│ └── token_status.rs # Token expiry/validity display
└── tests/ # Integration tests
├── cli_test.rs # End-to-end CLI tests
└── common/mod.rs # Shared test fixtures
Module Diagram¶
┌──────────┐
│ main.rs │
│ (entry) │
└────┬─────┘
│ parses args
v
┌──────────┐
│ cli.rs │
│ (clap) │
└────┬─────┘
│ dispatches to
v
┌──────────────┐
│ commands/ │
│ decode.rs │
│ verify.rs │
└───┬─────┬───┘
│ │
calls core │ │ calls display
v v
┌────────┐ ┌──────────┐
│ core/ │ │ display/ │
└────────┘ └──────────┘
Data flow:
main.rsparses CLI arguments viacli.rs(clap derive)- The parsed command is dispatched to the appropriate handler in
commands/ - Command handlers call into
core/for business logic - Command handlers call into
display/for terminal output - Errors from
core/(JwtTermError) are converted toanyhow::Errorat the command handler boundary
Module Responsibilities¶
main.rs -- Entry Point¶
- Declares
#![forbid(unsafe_code)]for the entire crate - Parses CLI arguments, matches subcommand, delegates to handler
- Returns
anyhow::Result<()>for automatic error formatting
cli.rs -- CLI Argument Definitions¶
- Clap derive structs for
Cli,Commands,DecodeArgs,VerifyArgs,CompletionsArgs - Custom
fmt::Debugon argument structs to redact sensitive fields
error.rs -- Domain Errors¶
JwtTermErrorenum usingthiserror::Error- Each variant represents a specific failure mode
- Error messages are user-facing and descriptive
core/decoder.rs -- JWT Decoding¶
- Splits raw JWT into three dot-separated parts
- Base64url-decodes header and payload
- Parses as JSON and returns
DecodedToken
core/validator.rs -- Signature Validation¶
- Auto-detects algorithm from token header
- Supports HMAC, RSA, RSA-PSS, ECDSA, EdDSA
- Sensitive key material is zeroized after use
core/jwks.rs -- Remote Key Fetching¶
- Fetches JWKS from HTTPS endpoints
- Matches keys by JWT
kidheader claim - Enforces HTTPS-only, 10s timeout, 1MB response limit
core/time_travel.rs -- Time Expression Parsing¶
- Parses relative expressions (
+7d,-1h) - Parses absolute ISO 8601 and Unix epoch timestamps
- Returns resolved timestamps for
exp/nbfevaluation
display/json_printer.rs -- JSON Output¶
- Colorized JSON: cyan keys, green strings, yellow numbers, magenta booleans, red null
- Plain mode (
--json) for machine-readable output
display/token_status.rs -- Token Status Display¶
- Human-readable temporal claim status
- Color-coded: red for expired, green for valid, yellow for not-yet-valid
Error Handling Strategy¶
Domain Layer (thiserror)¶
All business logic uses strongly typed errors:
#[derive(Debug, Error)]
pub enum JwtTermError {
#[error("invalid token format: ...")]
InvalidTokenFormat,
// ...
}
Functions in core/ return Result<T, JwtTermError>.
CLI Boundary (anyhow)¶
Command handlers return anyhow::Result<()>. The ? operator converts JwtTermError into anyhow::Error, and main() prints the error chain to stderr.
Security Architecture¶
Compiler-Level Safety¶
#![forbid(unsafe_code)]prevents anyunsafeblocks in the entire crate
Secret Handling¶
Zeroizing<String>andZeroizing<Vec<u8>>for all sensitive data- Custom
fmt::Debugredacts tokens and secrets with[REDACTED] - No logging of raw tokens, secrets, or key material
Network Security¶
- HTTPS-only JWKS fetching
- 10-second request timeout
- 1 MB response size limit
Privacy¶
- No telemetry or analytics
- No network requests beyond explicit
--jwks-url
Tech Stack¶
| Component | Technology |
|---|---|
| Language | Rust (2024 edition, MSRV 1.91) |
| CLI Framework | Clap v4 (derive) |
| JWT Validation | jsonwebtoken crate |
| HTTP Client | reqwest (rustls-tls) |
| Error Handling | thiserror (domain) + anyhow (CLI boundary) |
| Secret Safety | zeroize crate |
| JSON Parsing | serde_json |
| Timestamps | Chrono |
| Terminal Colors | colored crate |