@chengyixu/jwt-decode-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +132 -0
  2. package/index.js +258 -0
  3. package/package.json +37 -0
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # jwt-decode-cli
2
+
3
+ > Decode and inspect JWT tokens from the terminal. No external dependencies.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/jwt-decode-cli.svg)](https://www.npmjs.com/package/jwt-decode-cli)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ A fast, zero-dependency CLI tool to decode JSON Web Tokens (JWTs). Inspect headers, payloads, expiry status, and claim details — all from your terminal.
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g jwt-decode-cli
16
+ ```
17
+
18
+ Or run without installing:
19
+
20
+ ```bash
21
+ npx jwt-decode-cli <token>
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ # Pass token directly
30
+ jwt-decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
31
+
32
+ # Pipe from stdin
33
+ echo "eyJhbGci..." | jwt-decode
34
+ cat token.txt | jwt-decode
35
+
36
+ # Show help
37
+ jwt-decode --help
38
+
39
+ # Note signature verification requirement
40
+ jwt-decode <token> --verify
41
+
42
+ # Raw JSON output (no formatting)
43
+ jwt-decode <token> --raw
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Output
49
+
50
+ ```
51
+ ━━ Header ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
+ {
53
+ "alg": "HS256",
54
+ "typ": "JWT"
55
+ }
56
+
57
+ ━━ Payload ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
+ {
59
+ "sub": "1234567890",
60
+ "name": "John Doe",
61
+ "iat": 1516239022
62
+ }
63
+
64
+ ━━ Signature ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
65
+ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
66
+
67
+ ━━ Time Analysis ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
+ iat (issued at): 2018-01-18 01:30:22 UTC (7y 2m ago)
69
+ exp: ⚠ No expiry set — token never expires
70
+
71
+ ━━ Claims Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
72
+ sub (subject): 1234567890
73
+ custom claims: name
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Features
79
+
80
+ - **Zero dependencies** — uses only Node.js built-ins
81
+ - **Colored output** — syntax-highlighted JSON, color-coded expiry status
82
+ - **Expiry detection** — shows time until expiry or how long since expired
83
+ - **Stdin support** — pipe tokens from other commands
84
+ - **Claims summary** — highlights standard JWT claims (iss, sub, aud, jti, etc.)
85
+ - **Raw JSON mode** — `--raw` for scripting/piping to `jq`
86
+ - **Expired token warning** — bold red banner when token is expired
87
+ - **`--verify` flag** — placeholder with guidance for signature verification
88
+
89
+ ---
90
+
91
+ ## Flags
92
+
93
+ | Flag | Description |
94
+ |------|-------------|
95
+ | `--verify` | Notes that verification requires a secret/public key, provides guidance |
96
+ | `--raw` | Output raw JSON (no colors or formatting) |
97
+ | `--no-color` | Disable colored output (or set `NO_COLOR` env var) |
98
+ | `--help`, `-h` | Show help |
99
+
100
+ ---
101
+
102
+ ## Examples
103
+
104
+ ```bash
105
+ # Decode a token from an environment variable
106
+ jwt-decode $JWT_TOKEN
107
+
108
+ # Check token expiry in a script
109
+ jwt-decode $TOKEN --raw | jq '.payload.exp'
110
+
111
+ # Decode token stored in a file
112
+ cat ~/.config/token | jwt-decode
113
+
114
+ # Use in CI/CD to inspect tokens
115
+ echo $GITHUB_TOKEN | jwt-decode
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Security Note
121
+
122
+ This tool **decodes** JWTs but does **not verify** the signature. It is intended for development, debugging, and inspection purposes. Never trust a JWT's claims without verifying the signature using the appropriate secret or public key.
123
+
124
+ For signature verification, use:
125
+ - [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) npm package
126
+ - [jwt.io](https://jwt.io) (web-based debugger)
127
+
128
+ ---
129
+
130
+ ## License
131
+
132
+ MIT © chengyixu
package/index.js ADDED
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const args = process.argv.slice(2);
6
+
7
+ // ANSI color codes
8
+ const colors = {
9
+ reset: '\x1b[0m',
10
+ bold: '\x1b[1m',
11
+ dim: '\x1b[2m',
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ blue: '\x1b[34m',
16
+ magenta: '\x1b[35m',
17
+ cyan: '\x1b[36m',
18
+ white: '\x1b[37m',
19
+ bgRed: '\x1b[41m',
20
+ bgGreen: '\x1b[42m',
21
+ bgYellow: '\x1b[43m',
22
+ };
23
+
24
+ function c(color, text) {
25
+ // Disable colors if not a TTY or NO_COLOR env set
26
+ if (!process.stdout.isTTY || process.env.NO_COLOR) return text;
27
+ return colors[color] + text + colors.reset;
28
+ }
29
+
30
+ function printHelp() {
31
+ console.log(`
32
+ ${c('bold', 'jwt-decode-cli')} — Decode and inspect JWT tokens from the terminal
33
+
34
+ ${c('cyan', 'USAGE:')}
35
+ jwt-decode <token> Decode a JWT token
36
+ echo <token> | jwt-decode Decode via stdin
37
+ jwt-decode --help Show this help
38
+
39
+ ${c('cyan', 'FLAGS:')}
40
+ --verify Placeholder: signature verification (requires secret/key)
41
+ --raw Output raw JSON (no formatting)
42
+ --no-color Disable colored output
43
+ --help, -h Show help
44
+
45
+ ${c('cyan', 'EXAMPLES:')}
46
+ jwt-decode eyJhbGci...
47
+ cat token.txt | jwt-decode
48
+ jwt-decode eyJhbGci... --verify
49
+
50
+ ${c('cyan', 'OUTPUT:')}
51
+ - Header (algorithm, token type)
52
+ - Payload (claims, custom fields)
53
+ - Expiry status (valid, expired, or no expiry set)
54
+ - Time until expiration or time since expiry
55
+ - Issued-at and not-before times
56
+ `);
57
+ }
58
+
59
+ function base64urlDecode(str) {
60
+ // Convert base64url to base64
61
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
62
+ // Pad to multiple of 4
63
+ while (base64.length % 4 !== 0) {
64
+ base64 += '=';
65
+ }
66
+ try {
67
+ return Buffer.from(base64, 'base64').toString('utf8');
68
+ } catch (e) {
69
+ throw new Error('Failed to base64url-decode segment: ' + e.message);
70
+ }
71
+ }
72
+
73
+ function formatDuration(seconds) {
74
+ const abs = Math.abs(seconds);
75
+ if (abs < 60) return `${Math.floor(abs)}s`;
76
+ if (abs < 3600) return `${Math.floor(abs / 60)}m ${Math.floor(abs % 60)}s`;
77
+ if (abs < 86400) return `${Math.floor(abs / 3600)}h ${Math.floor((abs % 3600) / 60)}m`;
78
+ const days = Math.floor(abs / 86400);
79
+ const hours = Math.floor((abs % 86400) / 3600);
80
+ return `${days}d ${hours}h`;
81
+ }
82
+
83
+ function formatTimestamp(ts) {
84
+ try {
85
+ return new Date(ts * 1000).toISOString().replace('T', ' ').replace('.000Z', ' UTC');
86
+ } catch (e) {
87
+ return String(ts);
88
+ }
89
+ }
90
+
91
+ function prettyPrintJSON(obj, indent = 2) {
92
+ if (!process.stdout.isTTY || process.env.NO_COLOR) {
93
+ return JSON.stringify(obj, null, indent);
94
+ }
95
+ // Colorize JSON output
96
+ return JSON.stringify(obj, null, indent)
97
+ .replace(/"([^"]+)":/g, c('cyan', '"$1"') + ':')
98
+ .replace(/: "([^"]*)"/g, ': ' + c('green', '"$1"'))
99
+ .replace(/: (\d+\.?\d*)/g, ': ' + c('yellow', '$1'))
100
+ .replace(/: (true|false)/g, ': ' + c('magenta', '$1'))
101
+ .replace(/: (null)/g, ': ' + c('dim', '$1'));
102
+ }
103
+
104
+ function decodeJWT(token, opts = {}) {
105
+ const parts = token.split('.');
106
+
107
+ if (parts.length !== 3) {
108
+ console.error(c('red', `Error: Invalid JWT format. Expected 3 parts (header.payload.signature), got ${parts.length}.`));
109
+ process.exit(1);
110
+ }
111
+
112
+ const [headerB64, payloadB64, signatureB64] = parts;
113
+
114
+ // Decode header
115
+ let header;
116
+ try {
117
+ header = JSON.parse(base64urlDecode(headerB64));
118
+ } catch (e) {
119
+ console.error(c('red', 'Error: Could not decode JWT header: ' + e.message));
120
+ process.exit(1);
121
+ }
122
+
123
+ // Decode payload
124
+ let payload;
125
+ try {
126
+ payload = JSON.parse(base64urlDecode(payloadB64));
127
+ } catch (e) {
128
+ console.error(c('red', 'Error: Could not decode JWT payload: ' + e.message));
129
+ process.exit(1);
130
+ }
131
+
132
+ if (opts.raw) {
133
+ console.log(JSON.stringify({ header, payload, signature: signatureB64 }, null, 2));
134
+ return;
135
+ }
136
+
137
+ const now = Math.floor(Date.now() / 1000);
138
+
139
+ // ── HEADER ──────────────────────────────────────────────────────────────
140
+ console.log('\n' + c('bold', c('blue', '━━ Header ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
141
+ console.log(prettyPrintJSON(header));
142
+
143
+ // ── PAYLOAD ─────────────────────────────────────────────────────────────
144
+ console.log('\n' + c('bold', c('blue', '━━ Payload ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
145
+ console.log(prettyPrintJSON(payload));
146
+
147
+ // ── SIGNATURE ───────────────────────────────────────────────────────────
148
+ console.log('\n' + c('bold', c('blue', '━━ Signature ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
149
+ console.log(c('dim', signatureB64));
150
+
151
+ // ── TIME ANALYSIS ────────────────────────────────────────────────────────
152
+ console.log('\n' + c('bold', c('blue', '━━ Time Analysis ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
153
+
154
+ // Issued at (iat)
155
+ if (payload.iat !== undefined) {
156
+ const issuedAgo = now - payload.iat;
157
+ console.log(c('dim', ' iat (issued at): ') + c('white', formatTimestamp(payload.iat)) +
158
+ c('dim', ` (${formatDuration(issuedAgo)} ago)`));
159
+ }
160
+
161
+ // Not before (nbf)
162
+ if (payload.nbf !== undefined) {
163
+ const nbfDiff = now - payload.nbf;
164
+ const nbfStatus = nbfDiff >= 0
165
+ ? c('green', '✓ active')
166
+ : c('yellow', `⚠ not yet valid (in ${formatDuration(-nbfDiff)})`);
167
+ console.log(c('dim', ' nbf (not before): ') + c('white', formatTimestamp(payload.nbf)) +
168
+ ' ' + nbfStatus);
169
+ }
170
+
171
+ // Expiry (exp)
172
+ if (payload.exp !== undefined) {
173
+ const diff = payload.exp - now;
174
+ const expLine = c('dim', ' exp (expires): ') + c('white', formatTimestamp(payload.exp));
175
+
176
+ if (diff > 0) {
177
+ console.log(expLine + ' ' + c('green', `✓ valid (expires in ${formatDuration(diff)})`));
178
+ } else {
179
+ console.log(expLine + ' ' + c('red', `✗ EXPIRED ${formatDuration(diff)} ago`));
180
+ }
181
+ } else {
182
+ console.log(c('dim', ' exp: ') + c('yellow', '⚠ No expiry set — token never expires'));
183
+ }
184
+
185
+ // ── CLAIMS SUMMARY ───────────────────────────────────────────────────────
186
+ const standardClaims = new Set(['iss', 'sub', 'aud', 'exp', 'nbf', 'iat', 'jti']);
187
+ const customClaims = Object.keys(payload).filter(k => !standardClaims.has(k));
188
+
189
+ console.log('\n' + c('bold', c('blue', '━━ Claims Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
190
+
191
+ if (payload.iss) console.log(c('dim', ' iss (issuer): ') + c('white', payload.iss));
192
+ if (payload.sub) console.log(c('dim', ' sub (subject): ') + c('white', payload.sub));
193
+ if (payload.aud) {
194
+ const aud = Array.isArray(payload.aud) ? payload.aud.join(', ') : payload.aud;
195
+ console.log(c('dim', ' aud (audience): ') + c('white', aud));
196
+ }
197
+ if (payload.jti) console.log(c('dim', ' jti (JWT ID): ') + c('white', payload.jti));
198
+
199
+ if (customClaims.length > 0) {
200
+ console.log(c('dim', ` custom claims: `) + c('cyan', customClaims.join(', ')));
201
+ }
202
+
203
+ // ── VERIFICATION NOTICE ──────────────────────────────────────────────────
204
+ if (opts.verify) {
205
+ console.log('\n' + c('bold', c('blue', '━━ Verification ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')));
206
+ console.log(c('yellow', ' ⚠ --verify flag noted.'));
207
+ console.log(c('dim', ' Signature verification requires the secret key or public key.'));
208
+ console.log(c('dim', ' This tool decodes the token without verifying the signature.'));
209
+ console.log(c('dim', ' For full verification, use: https://jwt.io or a library like jsonwebtoken.'));
210
+ }
211
+
212
+ // ── EXPIRY BANNER ────────────────────────────────────────────────────────
213
+ if (payload.exp !== undefined && payload.exp < now) {
214
+ console.log('\n' + c('bgRed', c('bold', ' !! TOKEN IS EXPIRED !! ')));
215
+ }
216
+
217
+ console.log('');
218
+ }
219
+
220
+ // ── MAIN ──────────────────────────────────────────────────────────────────
221
+
222
+ const opts = {
223
+ verify: args.includes('--verify'),
224
+ raw: args.includes('--raw'),
225
+ };
226
+
227
+ // Handle --help / -h
228
+ if (args.includes('--help') || args.includes('-h')) {
229
+ printHelp();
230
+ process.exit(0);
231
+ }
232
+
233
+ // Filter out flags to get positional token argument
234
+ const positional = args.filter(a => !a.startsWith('--'));
235
+
236
+ if (positional.length > 0) {
237
+ // Token from CLI argument
238
+ decodeJWT(positional[0].trim(), opts);
239
+ } else if (!process.stdin.isTTY) {
240
+ // Token from stdin (pipe)
241
+ let data = '';
242
+ process.stdin.setEncoding('utf8');
243
+ process.stdin.on('data', chunk => { data += chunk; });
244
+ process.stdin.on('end', () => {
245
+ const token = data.trim();
246
+ if (!token) {
247
+ console.error(c('red', 'Error: No token provided via stdin.'));
248
+ printHelp();
249
+ process.exit(1);
250
+ }
251
+ decodeJWT(token, opts);
252
+ });
253
+ } else {
254
+ // No input at all
255
+ console.error(c('red', 'Error: No JWT token provided.\n'));
256
+ printHelp();
257
+ process.exit(1);
258
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@chengyixu/jwt-decode-cli",
3
+ "version": "1.0.0",
4
+ "description": "Decode and inspect JWT tokens from the terminal. View header, payload, expiry, and more.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "jwt-decode": "index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node index.js eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
11
+ },
12
+ "keywords": [
13
+ "jwt",
14
+ "token",
15
+ "decode",
16
+ "auth",
17
+ "cli",
18
+ "security",
19
+ "json-web-token",
20
+ "inspect",
21
+ "debug",
22
+ "authentication"
23
+ ],
24
+ "author": "chengyixu",
25
+ "license": "MIT",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/chengyixu/jwt-decode-cli.git"
32
+ },
33
+ "homepage": "https://github.com/chengyixu/jwt-decode-cli#readme",
34
+ "engines": {
35
+ "node": ">=12.0.0"
36
+ }
37
+ }