-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjwt-decoder.html
More file actions
1146 lines (1021 loc) · 68.2 KB
/
Copy pathjwt-decoder.html
File metadata and controls
1146 lines (1021 loc) · 68.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JWT Decoder — Inspect JSON Web Tokens | FreeDevTool</title>
<meta name="description" content="Free JWT decoder. Decode bearer tokens, inspect header/payload/expiry, validate signatures (HS256, RS256, ES256). Runs in browser, tokens stay local.">
<meta name="robots" content="index, follow">
<meta name="author" content="Anees Ur Rehman">
<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","datePublished":"2026-05-02","dateModified":"2026-05-19","inLanguage":"en-US","isPartOf":{"@type":"WebSite","name":"FreeDevTool","url":"https://freedevtool.org"}}</script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"Person","name":"Anees Ur Rehman","url":"https://freedevtool.org/about","jobTitle":"Full-stack developer","worksFor":{"@type":"Organization","name":"FreeDevTool","url":"https://freedevtool.org"}}</script>
<link rel="canonical" href="https://freedevtool.org/jwt-decoder">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap" as="style">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap">
<link rel="preload" href="style.css?v=20260502-cards" as="style">
<link rel="stylesheet" href="style.css?v=20260502-cards">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/favicon.svg">
<meta property="og:image" content="https://freedevtool.org/og-image.svg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="FreeDevTool — 50+ free, fast, privacy-first developer tools">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://freedevtool.org/og-image.svg">
<meta name="twitter:title" content="JWT Decoder Online — Free | FreeDevTool">
<meta name="twitter:description" content="Decode JWT tokens, inspect header, payload & expiration. Browser-based, no signup, tokens never sent to a server.">
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3L0CMH3X36"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-3L0CMH3X36');
</script>
<meta property="og:title" content="JWT Decoder — FreeDevTool">
<meta property="og:description" content="Free online JWT token decoder. Instant, secure, no sign-up needed.">
<meta property="og:url" content="https://freedevtool.org/jwt-decoder">
<meta property="og:type" content="website">
<meta property="og:site_name" content="FreeDevTool">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "JWT Decoder",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web Browser",
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"description": "Free online JWT token decoder and verifier"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is the difference between decoding and verifying a JWT?",
"acceptedAnswer": { "@type": "Answer", "text": "Decoding a JWT simply extracts the header and payload by Base64URL-decoding them — no secret key is needed. Verifying a JWT checks the signature against a secret or public key to confirm the token hasn't been tampered with. This tool decodes JWTs for inspection; always verify signatures server-side before trusting claims." }
},
{
"@type": "Question",
"name": "Is it safe to decode a JWT token online?",
"acceptedAnswer": { "@type": "Answer", "text": "It depends on the tool. This JWT decoder runs 100% in your browser — no data is ever sent to a server. Your token never leaves your device. Avoid online decoders that submit tokens to backend servers, as JWTs can contain sensitive user data and session information." }
},
{
"@type": "Question",
"name": "Can you decode a JWT without the secret key?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. JWT headers and payloads are Base64URL-encoded, not encrypted. Anyone can decode them without a key. The secret key is only needed to verify the signature. This is why you should never store sensitive data like passwords directly in a JWT payload." }
},
{
"@type": "Question",
"name": "What does the exp claim mean in a JWT?",
"acceptedAnswer": { "@type": "Answer", "text": "The 'exp' (expiration time) claim identifies the time after which the JWT must not be accepted for processing. It is a UNIX timestamp (seconds since 1970-01-01). If the current time is past the exp value, the token is expired and should be rejected by the server." }
},
{
"@type": "Question",
"name": "Should I store sensitive data in a JWT?",
"acceptedAnswer": { "@type": "Answer", "text": "No. JWT payloads are only Base64URL-encoded, not encrypted. Anyone who intercepts a JWT can read its contents. Store only non-sensitive identifiers (user ID, role, permissions) in the payload. For truly confidential data, use JWE (JSON Web Encryption) or keep the data server-side." }
},
{
"@type": "Question",
"name": "What is the difference between JWT and JWE?",
"acceptedAnswer": { "@type": "Answer", "text": "A JWT (JSON Web Token) with JWS (JSON Web Signature) has a readable payload signed for integrity — anyone can read the claims but can't modify them. A JWE (JSON Web Encryption) encrypts the payload so only authorized parties with the decryption key can read the contents. Use JWE when the payload itself must remain confidential." }
},{"@type":"Question","name":"What does the exp claim mean in a JWT?","acceptedAnswer":{"@type":"Answer","text":"exp (expiration time) is a Unix timestamp after which the JWT must not be accepted. Verifiers reject tokens where the current time is past exp. Recommended JWT lifetimes are short (15-60 minutes for access tokens), paired with longer-lived refresh tokens for renewal."}},{"@type":"Question","name":"What does RS256 vs HS256 mean in JWT?","acceptedAnswer":{"@type":"Answer","text":"HS256 uses HMAC-SHA256 with a shared secret — anyone who can verify can also forge. RS256 uses RSA-SHA256 with an asymmetric keypair — the issuer signs with the private key, verifiers use the public key. Use RS256 when the verifier is a different service from the issuer."}},{"@type":"Question","name":"What is the difference between JWT and a Bearer token?","acceptedAnswer":{"@type":"Answer","text":"A Bearer token is any opaque token presented in the HTTP Authorization header as Authorization: Bearer token. A JWT is a specific structured Bearer token format (header.payload.signature). All JWTs are Bearer tokens; not all Bearer tokens are JWTs."}}
]
}
</script>
<style>
.jwt-parts { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.jwt-part {
background: var(--bg3); border: 1px solid var(--border);
border-radius: var(--radius); overflow: hidden;
transition: border-color .2s;
}
.jwt-part:hover { border-color: var(--border2); }
.jwt-part-header {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px;
background: var(--bg4);
border-bottom: 1px solid var(--border);
}
.jwt-part-label {
font-family: var(--mono); font-size: 11px;
font-weight: 600; text-transform: uppercase; letter-spacing: .5px;
}
.jwt-part-label.header-label { color: #ff6b6b; }
.jwt-part-label.payload-label { color: #a78bfa; }
.jwt-part-label.sig-label { color: #4ecdc4; }
.jwt-part-body {
padding: 14px;
font-family: var(--mono); font-size: 13px;
color: var(--text); white-space: pre-wrap; word-break: break-all;
line-height: 1.65;
}
.claim-row {
display: flex; justify-content: space-between; align-items: baseline;
padding: 6px 0;
border-bottom: 1px solid var(--border);
}
.claim-row:last-child { border-bottom: none; }
.claim-key { color: var(--accent); font-weight: 500; }
.claim-val { color: var(--text2); text-align: right; max-width: 60%; word-break: break-all; }
.claim-time { font-size: 11px; color: var(--text3); display: block; }
.jwt-color-header { color: #ff6b6b; }
.jwt-color-payload { color: #a78bfa; }
.jwt-color-sig { color: #4ecdc4; }
.jwt-raw {
font-family: var(--mono); font-size: 12px;
color: var(--text2); word-break: break-all; line-height: 1.6;
padding: 12px 14px;
background: var(--bg3); border: 1px solid var(--border);
border-radius: var(--radius); margin-top: 10px;
}
.exp-badge {
display: inline-flex; align-items: center; gap: 4px;
font-family: var(--mono); font-size: 10px; font-weight: 600;
padding: 2px 8px; border-radius: 4px;
text-transform: uppercase; letter-spacing: .4px;
}
.exp-valid { background: rgba(0,208,132,.12); color: var(--accent); }
.exp-expired { background: rgba(255,90,90,.12); color: var(--red); }
</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://freedevtool.org/"
},
{
"@type": "ListItem",
"position": 2,
"name": "All Tools",
"item": "https://freedevtool.org/all-tools"
},
{
"@type": "ListItem",
"position": 3,
"name": "JWT Decoder & Token Inspector",
"item": "https://freedevtool.org/jwt-decoder"
}
]
}
</script>
<script src="/ga4-events.js" defer></script>
</head>
<body>
<nav>
<a class="nav-logo" href="/" aria-label="FreeDevTool home"><svg class="logo-mark" width="22" height="22" viewBox="0 0 24 24" aria-hidden="true" fill="none"><rect x="1" y="1" width="22" height="22" rx="6" fill="currentColor" opacity=".12"/><path d="M9.5 8.5L6 12l3.5 3.5M14.5 8.5L18 12l-3.5 3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>FreeDevTool</a>
<div class="nav-links">
<div class="nav-dropdown" id="tools-dropdown">
<a href="all-tools" onclick="event.preventDefault();this.parentElement.classList.toggle('open')" aria-haspopup="true">Tools <svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></a>
<div class="nav-dropdown-menu">
<a href="encoding-tools">
<div class="dd-icon">b64</div>
<div class="dd-info"><div class="dd-name">Encoding & Conversion</div><div class="dd-count">11 tools · Base64, YAML, px→rem</div></div>
</a>
<a href="generation-tools">
<div class="dd-icon">{ }</div>
<div class="dd-info"><div class="dd-name">Generation & Formatting</div><div class="dd-count">16 tools · JSON, SQL, gradients</div></div>
</a>
<a href="security-tools">
<div class="dd-icon">#</div>
<div class="dd-info"><div class="dd-name">Security & Hashing</div><div class="dd-count">3 tools · JWT, MD5, SHA</div></div>
</a>
<a href="text-tools">
<div class="dd-icon">.*</div>
<div class="dd-info"><div class="dd-name">Code & Text Tools</div><div class="dd-count">9 tools · Regex, diff, tokens</div></div>
</a>
<a href="devops-tools">
<div class="dd-icon">JS</div>
<div class="dd-info"><div class="dd-name">Optimization & DevOps</div><div class="dd-count">7 tools · Minifiers, cURL, git</div></div>
</a>
<a href="network-tools">
<div class="dd-icon">IP</div>
<div class="dd-info"><div class="dd-name">Network & Time</div><div class="dd-count">4 tools · IP, DNS, timestamps</div></div>
</a>
<a href="seo-tools">
<div class="dd-icon">SEO</div>
<div class="dd-info"><div class="dd-name">SEO & Meta Tools</div><div class="dd-count">3 tools · OG, meta, slug</div></div>
</a>
<div class="nav-dropdown-divider"></div>
<a class="dd-all" href="all-tools">
<div class="dd-icon">All</div>
<div class="dd-info"><div class="dd-name">Browse all 50 tools</div><div class="dd-count">Searchable catalog & categories</div></div>
</a>
</div>
</div>
<a href="/guides">Guides</a>
<a href="about">About</a>
<a href="privacy">Privacy</a>
</div>
</nav>
<div id="copy-toast">Copied!</div>
<div class="wrapper">
<a class="tool-back" href="/" aria-label="Back to home">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>
Back
</a>
<div class="tool-header">
<div class="tool-badge">Security Tool</div>
<h1>JWT Decoder Online — Inspect JSON Web Tokens</h1>
<p class="tool-description">
Decode any JSON Web Token (JWT) instantly and inspect its header, payload, and claims. This free JWT decoder parses tokens per <a href="https://datatracker.ietf.org/doc/html/rfc7519" rel="noopener" style="color:var(--accent)">RFC 7519</a>, displays standard claims (<code>iss</code>, <code>sub</code>, <code>aud</code>, <code>exp</code>, <code>iat</code>, <code>nbf</code>, <code>jti</code>) with human-readable timestamps, and highlights expiration in real time. Paste an OAuth 2.0 bearer token, an API access token, or any JWS to view its contents. All processing runs in your browser — your token never reaches a server, and nothing is logged.
</p>
<div class="last-updated">Last updated: May 2026 · Written by <a href="/about">Anees Ur Rehman</a>, full-stack developer</div>
</div>
<div class="tool-card">
<div class="tool-card-header">
<div class="dot dot-red"></div>
<div class="dot dot-yellow"></div>
<div class="dot dot-green"></div>
<span class="tool-card-title">jwt-decoder.tool</span>
</div>
<div class="tool-body">
<label>Paste your JWT token</label>
<textarea id="jwt-input" placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" oninput="decodeJWT()" rows="4"></textarea>
<div id="jwt-error" style="display:none"></div>
<div id="jwt-result" style="display:none">
<div class="divider"></div>
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:8px">
<label style="margin:0">Token Status</label>
<span id="exp-status"></span>
</div>
<div class="jwt-parts">
<!-- Header -->
<div class="jwt-part">
<div class="jwt-part-header">
<span class="jwt-part-label header-label">Header (JOSE)</span>
<button class="btn btn-ghost" onclick="copyPart('header')">Copy</button>
</div>
<div class="jwt-part-body" id="jwt-header"></div>
</div>
<!-- Payload -->
<div class="jwt-part">
<div class="jwt-part-header">
<span class="jwt-part-label payload-label">Payload (Claims)</span>
<button class="btn btn-ghost" onclick="copyPart('payload')">Copy</button>
</div>
<div class="jwt-part-body" id="jwt-payload"></div>
</div>
<!-- Signature -->
<div class="jwt-part">
<div class="jwt-part-header">
<span class="jwt-part-label sig-label">Signature</span>
<button class="btn btn-ghost" onclick="copyPart('signature')">Copy</button>
</div>
<div class="jwt-part-body" id="jwt-signature" style="font-size:12px; color:var(--text2)"></div>
</div>
</div>
<div class="divider"></div>
<label>Claims Table</label>
<div id="jwt-claims"></div>
<div class="divider"></div>
<label>Color-Coded Raw Token</label>
<div class="jwt-raw" id="jwt-raw-colored"></div>
</div>
</div>
</div>
<!-- =============================================================
LONG-FORM ARTICLE — comprehensive guide for E-E-A-T + ranking.
============================================================= -->
<article>
<p class="aeo-lead" style="font-size:16px;line-height:1.7;color:var(--text);max-width:760px;margin:24px auto 18px;padding:0 4px">
<strong>A JSON Web Token (JWT, RFC 7519)</strong> is a Base64URL-encoded string with three dot-separated parts: <code>header.payload.signature</code>. The header and payload are <em>not encrypted</em> — they can be read by anyone holding the token. Only the signature requires a key to verify. This <strong>free JWT decoder</strong> parses any token entirely in your browser — open the DevTools Network tab and you will see zero requests fire when you paste. Useful for inspecting bearer tokens, OAuth access tokens, and OpenID Connect ID tokens without sending them to a server.
</p>
<section id="examples" style="max-width:760px;margin:24px auto 32px">
<h2 style="font-size:18px;margin-bottom:14px">Examples</h2>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Decoded JWT header</strong>
<code style="display:block;font-family:var(--mono);font-size:13px;line-height:1.6">{<br> "alg": "RS256",<br> "typ": "JWT",<br> "kid": "abc123"<br>}</code>
<p style="margin:6px 0 0;font-size:13px">Identifies the signing algorithm (HS256, RS256, ES256) and key ID used to sign.</p>
</div>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;margin-bottom:12px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Decoded JWT payload (claims)</strong>
<code style="display:block;font-family:var(--mono);font-size:13px;line-height:1.6">{<br> "sub": "1234567890",<br> "iat": 1717094400,<br> "exp": 1717180800,<br> "scope": "read:profile"<br>}</code>
<p style="margin:6px 0 0;font-size:13px">Common claims: <code>sub</code> (subject/user ID), <code>iat</code> (issued at), <code>exp</code> (expiry), <code>aud</code> (audience), <code>iss</code> (issuer).</p>
</div>
<div style="background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px">
<strong style="display:block;color:var(--accent);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Bearer token from Authorization header</strong>
<code style="display:block;font-family:var(--mono);font-size:13px;line-height:1.6">Authorization: Bearer eyJhbGciOi...XVCJ9.eyJzdWIi...wMH0.SflKxw...</code>
<p style="margin:6px 0 0;font-size:13px">Paste just the token (everything after "Bearer ") into the decoder.</p>
</div>
</section>
<aside class="founder-note" style="max-width:760px;margin:24px auto 32px;padding:20px 24px;background:rgba(0,208,132,0.05);border-left:3px solid var(--accent);border-radius:6px;font-size:14px;line-height:1.7;color:var(--text2)">
<div style="font-family:var(--mono);font-size:11px;color:var(--accent);letter-spacing:1.5px;text-transform:uppercase;margin-bottom:10px;font-weight:600">💡 Why I built this</div>
<p style="margin:0 0 12px">I built this after a coworker showed me their JWT debug workflow: paste it into jwt.io, see the payload. Sounds fine — except the token they were debugging belonged to a real customer, and jwt.io is owned by Auth0/Okta. Even if Auth0 doesn't log the tokens (and I have no evidence they do), the token had now traversed their infrastructure. This decoder runs entirely in your browser. Open DevTools, switch to the Network tab, paste a token — zero requests fire. The token never leaves your machine. Verify it yourself.</p>
<p style="margin:0;font-size:13px;color:var(--text3)">— <a href="/about" style="color:var(--accent);text-decoration:none">Anees Ur Rehman</a>, full-stack developer</p>
</aside>
<section class="article-section">
<h2>What is a JWT?</h2>
<p>A <strong>JSON Web Token (JWT)</strong> is a compact, URL-safe way to represent <em>signed</em> claims between two parties. Defined in <a href="https://datatracker.ietf.org/doc/html/rfc7519" rel="noopener">RFC 7519</a>, JWT became the dominant token format for stateless authentication after OAuth 2.0 (RFC 6749) adopted it as the default bearer token in OpenID Connect. Today, every major identity provider — Auth0, AWS Cognito, Firebase Auth, Okta, Keycloak, Azure AD — issues JWTs. Most modern APIs verify them on every request.</p>
<p>The killer feature is <strong>statelessness</strong>: instead of storing session data on the server (with the database lookup that implies), the server signs a token containing the user's identity, expiration, and permissions. Every subsequent request includes the token; the server verifies the signature with a single cryptographic check — no database hit. Trade-off: you can't revoke a token before it expires (without bringing back state).</p>
<p>A JWT is <strong>signed, not encrypted</strong>. Anyone holding the token can read its contents. The signature only proves the issuer hasn't been tampered with — it doesn't hide the data. This is why <strong>you should never put secrets in a JWT payload.</strong> If you need encryption, use JWE (JSON Web Encryption, RFC 7516) — the lesser-known sibling of JWT.</p>
</section>
<section class="article-section">
<h2>JWT structure — three parts separated by dots</h2>
<p>Every JWT is exactly three Base64URL-encoded segments joined by periods:</p>
<div class="lang-block">
<div class="lang-block-header">jwt-format</div>
<pre><code><header>.<payload>.<signature>
// Real example (formatted for readability):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFuZWVzIiwiaWF0IjoxNzE5NTAwMDAwLCJleHAiOjE3MTk1MDM2MDB9
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
</code></pre>
</div>
<h3>1. Header</h3>
<p>A JSON object encoding the signing algorithm and token type. Decoded:</p>
<div class="lang-block">
<div class="lang-block-header">json</div>
<pre><code>{ "alg": "HS256", "typ": "JWT" }</code></pre>
</div>
<p>The <code>alg</code> field is critical — it tells the verifier which algorithm to use to validate the signature. Common values: <code>HS256</code> (HMAC-SHA256, symmetric), <code>RS256</code> (RSA + SHA-256, asymmetric), <code>ES256</code> (ECDSA P-256). The optional <code>kid</code> (key ID) field tells the verifier <em>which</em> key to use when keys are rotated.</p>
<h3>2. Payload</h3>
<p>A JSON object containing the <strong>claims</strong> — statements about the user and the token itself. Mix of standard (registered) claims and custom (private) claims:</p>
<div class="lang-block">
<div class="lang-block-header">json</div>
<pre><code>{
"sub": "1234", // subject — the user
"iss": "https://auth.example.com", // issuer
"aud": "api.example.com", // audience — who can use this
"exp": 1719503600, // expiration — Unix seconds
"iat": 1719500000, // issued at
"nbf": 1719500000, // not before
"jti": "a3f7c1...", // unique token ID (for revocation lists)
"role": "admin", // custom claim
"scope": "read write" // custom claim
}</code></pre>
</div>
<h3>3. Signature</h3>
<p>The signature is computed over the encoded header and payload using the algorithm specified in the header. For HS256:</p>
<div class="lang-block">
<div class="lang-block-header">signature-formula</div>
<pre><code>signature = HMAC-SHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)</code></pre>
</div>
<p>The verifier recomputes this over the received header and payload using its copy of the secret (or public key for RS256/ES256). If the signatures match, the token is authentic. If not, reject it.</p>
</section>
<section class="article-section">
<h2>JWT signing algorithms — HS256 vs RS256 vs ES256</h2>
<p>Choosing the algorithm depends on whether you control both ends of the trust relationship.</p>
<table class="ref-table">
<thead>
<tr><th>Algorithm</th><th>Type</th><th>Key</th><th>When to use</th><th>Trade-offs</th></tr>
</thead>
<tbody>
<tr>
<td><strong>HS256</strong> (HMAC-SHA256)</td>
<td>Symmetric</td>
<td>Shared secret (same key signs and verifies)</td>
<td>Single-service apps where the signer and verifier are the same backend</td>
<td>Anyone who can verify can also forge tokens. Don't use across multiple services.</td>
</tr>
<tr>
<td><strong>RS256</strong> (RSA-PKCS1-v1.5 + SHA-256)</td>
<td>Asymmetric</td>
<td>RSA private key signs, public key verifies (2048+ bit)</td>
<td>Multi-service architectures, third-party integrations, OpenID Connect</td>
<td>Larger signatures (256 bytes), slower than HS256, but verifiers don't need the secret.</td>
</tr>
<tr>
<td><strong>ES256</strong> (ECDSA P-256 + SHA-256)</td>
<td>Asymmetric</td>
<td>Elliptic curve private key signs, public key verifies</td>
<td>Same as RS256 but smaller and faster — modern preferred choice</td>
<td>Slightly newer; less universal library support than RS256.</td>
</tr>
<tr>
<td><strong>EdDSA</strong> (Ed25519)</td>
<td>Asymmetric</td>
<td>Ed25519 keys — fast, modern</td>
<td>New systems where library support exists</td>
<td>Best modern choice, but check verifier support before adopting.</td>
</tr>
<tr>
<td><strong>none</strong></td>
<td>(no signature)</td>
<td>—</td>
<td><strong>Never in production.</strong> Source of multiple historical vulnerabilities.</td>
<td>Servers must explicitly reject <code>alg: none</code>.</td>
</tr>
</tbody>
</table>
<p><strong>Rule of thumb:</strong> use <strong>RS256 or ES256 across services</strong> (so verifiers don't need your secret), <strong>HS256 only inside a single service</strong>, and <strong>never <code>none</code></strong>.</p>
</section>
<section class="article-section">
<h2>Standard JWT claims explained</h2>
<p>RFC 7519 §4.1 defines seven <strong>registered claims</strong>. Every JWT library understands these, so use them rather than custom equivalents.</p>
<table class="ref-table">
<thead><tr><th>Claim</th><th>Full name</th><th>Type</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><code>iss</code></td><td>Issuer</td><td>String / URL</td><td>Who issued this token. Verifiers should check this matches a trusted issuer.</td></tr>
<tr><td><code>sub</code></td><td>Subject</td><td>String</td><td>Whom the token is about. Usually a user ID.</td></tr>
<tr><td><code>aud</code></td><td>Audience</td><td>String or array</td><td>Who is allowed to use this token. Verifiers must check this matches their service.</td></tr>
<tr><td><code>exp</code></td><td>Expiration</td><td>NumericDate (Unix seconds)</td><td>After this time, the token is invalid. <strong>Mandatory in practice.</strong></td></tr>
<tr><td><code>nbf</code></td><td>Not Before</td><td>NumericDate</td><td>Token is not valid before this time. Useful for delayed activation.</td></tr>
<tr><td><code>iat</code></td><td>Issued At</td><td>NumericDate</td><td>When the token was issued. Useful for revocation by issue time.</td></tr>
<tr><td><code>jti</code></td><td>JWT ID</td><td>String</td><td>Unique identifier for the token. Enables revocation lists.</td></tr>
</tbody>
</table>
<p>Custom claims should be namespaced when used across organizations — RFC 7519 §4.3 recommends using a URI-prefixed name like <code>"https://example.com/role": "admin"</code> to avoid collisions with future registered claims.</p>
</section>
<section class="article-section">
<h2>JWT verification flow — the complete checklist</h2>
<p>Decoding a JWT is trivial. <strong>Verifying</strong> it correctly is what matters. A complete server-side check covers eight steps:</p>
<ul>
<li><strong>1. Parse the three parts.</strong> Split on dots; reject if not exactly three segments.</li>
<li><strong>2. Decode header.</strong> Base64URL-decode and parse JSON. Read <code>alg</code> and <code>kid</code>.</li>
<li><strong>3. Validate the algorithm.</strong> Whitelist allowed algorithms in your library. <strong>Never trust <code>alg</code> from the token alone</strong> — that's the alg-confusion attack. Configure your library to accept only <code>RS256</code> (or whichever you use).</li>
<li><strong>4. Look up the key.</strong> For HS256, your shared secret. For RS256/ES256, fetch the public key by <code>kid</code> from your JWKS endpoint (<code>/.well-known/jwks.json</code>).</li>
<li><strong>5. Verify the signature.</strong> Recompute it over <code>header.payload</code> and compare with constant-time equality.</li>
<li><strong>6. Check expiration.</strong> Reject if <code>exp</code> is missing OR <code>now() > exp</code> (with a small leeway, e.g. ±60 seconds for clock skew).</li>
<li><strong>7. Check audience and issuer.</strong> Verify <code>iss</code> matches your trusted issuer. Verify <code>aud</code> contains your service's identifier.</li>
<li><strong>8. Check revocation if you maintain a list.</strong> Look up <code>jti</code> in your revocation cache; reject if present.</li>
</ul>
<div class="article-aside">
<strong>This decoder does steps 1–2 only.</strong> It exists to let you inspect a token. <strong>Always verify on the server</strong> with a battle-tested library: <code>jose</code> (JS/Node), <code>PyJWT</code> (Python), <code>jjwt</code> (Java), <code>golang-jwt/jwt</code> (Go), or your auth provider's SDK.
</div>
</section>
<section class="article-section">
<h2>Verifying JWTs in 8 programming languages</h2>
<h3>Node.js (jose)</h3>
<div class="lang-block">
<div class="lang-block-header">node.js</div>
<pre><code>import { jwtVerify, createRemoteJWKSet } from 'jose';
// Auto-fetches keys from the issuer's JWKS endpoint
const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://auth.example.com',
audience: 'api.example.com',
algorithms: ['RS256'], // whitelist!
});
console.log(payload.sub);
</code></pre>
</div>
<h3>Python (PyJWT)</h3>
<div class="lang-block">
<div class="lang-block-header">python</div>
<pre><code>import jwt
# Symmetric (HS256)
decoded = jwt.decode(token, secret, algorithms=["HS256"],
audience="api.example.com",
issuer="https://auth.example.com")
# Asymmetric (RS256) with PyJWT >= 2.0
decoded = jwt.decode(
token, public_key, algorithms=["RS256"],
audience="api.example.com",
)
print(decoded["sub"])
</code></pre>
</div>
<h3>PHP (firebase/php-jwt)</h3>
<div class="lang-block">
<div class="lang-block-header">php</div>
<pre><code>use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// HS256
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
// RS256
$decoded = JWT::decode($token, new Key($publicKey, 'RS256'));
echo $decoded->sub;
</code></pre>
</div>
<h3>Java (JJWT)</h3>
<div class="lang-block">
<div class="lang-block-header">java</div>
<pre><code>import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
Jws<Claims> jws = Jwts.parser()
.verifyWith(publicKey) // RSA public key
.requireIssuer("https://auth.example.com")
.requireAudience("api.example.com")
.build()
.parseSignedClaims(token);
String userId = jws.getPayload().getSubject();
</code></pre>
</div>
<h3>Go (golang-jwt/jwt)</h3>
<div class="lang-block">
<div class="lang-block-header">go</div>
<pre><code>import "github.com/golang-jwt/jwt/v5"
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected algorithm: %v", t.Header["alg"])
}
return publicKey, nil
}, jwt.WithAudience("api.example.com"),
jwt.WithIssuer("https://auth.example.com"))
</code></pre>
</div>
<h3>Rust (jsonwebtoken)</h3>
<div class="lang-block">
<div class="lang-block-header">rust</div>
<pre><code>use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&["api.example.com"]);
validation.set_issuer(&["https://auth.example.com"]);
let key = DecodingKey::from_rsa_pem(public_key_pem)?;
let token = decode::<Claims>(token_str, &key, &validation)?;
</code></pre>
</div>
<h3>Ruby (jwt gem)</h3>
<div class="lang-block">
<div class="lang-block-header">ruby</div>
<pre><code>require 'jwt'
decoded = JWT.decode(token, public_key, true, {
algorithm: 'RS256',
iss: 'https://auth.example.com',
verify_iss: true,
aud: 'api.example.com',
verify_aud: true,
})
payload = decoded[0]
</code></pre>
</div>
<h3>C# / .NET</h3>
<div class="lang-block">
<div class="lang-block-header">csharp</div>
<pre><code>using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
var handler = new JwtSecurityTokenHandler();
var validation = new TokenValidationParameters {
ValidIssuer = "https://auth.example.com",
ValidAudience = "api.example.com",
IssuerSigningKey = new RsaSecurityKey(publicKey),
ValidAlgorithms = new[] { "RS256" },
};
handler.ValidateToken(token, validation, out var validatedToken);
</code></pre>
</div>
</section>
<section class="article-section">
<h2>Common JWT vulnerabilities — and how to avoid them</h2>
<h3>1. Algorithm confusion (<code>alg: none</code>)</h3>
<p>Some libraries accept <code>"alg": "none"</code> in the header, which means "no signature." An attacker forges a token with <code>alg: none</code> and an empty signature; if your library trusts the header, the token is "valid." <strong>Fix:</strong> always whitelist allowed algorithms server-side. Never accept <code>none</code>.</p>
<h3>2. RS256 → HS256 substitution</h3>
<p>If your verifier accepts both <code>HS256</code> and <code>RS256</code>, an attacker can take your <strong>public</strong> RSA key (which is not a secret), use it as the HMAC secret, and forge a token signed as <code>HS256</code>. Your library validates with the public key as if it were an HMAC secret. <strong>Fix:</strong> hard-code a single algorithm per endpoint, or only allow algorithms that match the key type.</p>
<h3>3. Unverified <code>kid</code> path traversal</h3>
<p>The <code>kid</code> header is attacker-controlled. If your code does <code>readFile(<span style="color:var(--text2)">"/keys/" + kid + ".pem"</span>)</code>, an attacker sets <code>kid</code> to <code>../../etc/passwd</code> and reads arbitrary files. <strong>Fix:</strong> look up keys in a whitelist or hashmap, never construct file paths from <code>kid</code>.</p>
<h3>4. Missing or wrong audience check</h3>
<p>Token issued for <em>service A</em> gets replayed against <em>service B</em>. If service B doesn't check <code>aud</code>, it accepts the token. <strong>Fix:</strong> always validate <code>aud</code> matches your service identifier.</p>
<h3>5. Long expiration windows</h3>
<p>Tokens with <code>exp</code> 30 days out are catastrophic if leaked. <strong>Fix:</strong> short access tokens (5–60 minutes) + refresh tokens stored server-side. Rotate refresh tokens on use.</p>
<h3>6. Storing secrets in payload</h3>
<p>Payloads are Base64URL, not encrypted. Anyone with the token reads them. <strong>Fix:</strong> only put non-sensitive identifiers in JWTs. Use JWE if you need encryption.</p>
<h3>7. Client-side signature verification</h3>
<p>"Verifying" a JWT in the browser is theater — the user controls the browser. <strong>Fix:</strong> always verify on the server. Browser-side, you can read the payload to display the user's name, but never base authorization decisions on it.</p>
</section>
<section class="article-section">
<h2>JWT vs sessions vs API keys — when to use what</h2>
<table class="ref-table">
<thead><tr><th>Mechanism</th><th>Stateful?</th><th>Best for</th><th>Trade-offs</th></tr></thead>
<tbody>
<tr>
<td><strong>JWT (Bearer)</strong></td>
<td>No (stateless)</td>
<td>Microservices, mobile/SPA → API, OpenID Connect, federated identity</td>
<td>Hard to revoke before expiration. Larger than session cookies.</td>
</tr>
<tr>
<td><strong>Session cookie + server store</strong></td>
<td>Yes</td>
<td>Traditional web apps, immediate revocation, server-rendered pages</td>
<td>DB lookup per request. Tightly tied to one origin.</td>
</tr>
<tr>
<td><strong>API key</strong></td>
<td>Yes (DB lookup)</td>
<td>Server-to-server APIs, long-lived integrations, public-facing APIs</td>
<td>Long-lived. Compromise = rotate key. No claims.</td>
</tr>
<tr>
<td><strong>OAuth 2.0 access token (JWT or opaque)</strong></td>
<td>Depends on issuer</td>
<td>Third-party integrations, "Login with X"</td>
<td>Spec compliance overhead. Requires identity provider.</td>
</tr>
</tbody>
</table>
<p>The wider industry has moved toward <strong>short-lived JWTs + refresh tokens</strong> for client-server auth, but plain old session cookies are still the simpler choice for monolithic apps where you don't need cross-service authentication.</p>
</section>
<section class="article-section">
<h2>Decode JWT, parse JWT, decode bearer token — what people search for</h2>
<p>"Decode JWT", "jwt decode", "jwt token decode", "parse jwt", "parse jwt token", "json web token decode", "json web signature decode", "bearer token decode", "decode oauth token" — Google clusters these together because they're all the same intent: paste an encoded token, see the decoded header and payload, check expiry. The decoder above handles every form. Each section below answers one of the recurring sub-questions.</p>
<h3>Bearer token decode — what an Authorization header actually contains</h3>
<p>The HTTP header <code>Authorization: Bearer eyJhbGciOiJIUzI1NiIs…</code> carries a JWT in the value after <code>Bearer </code>. Strip the <code>Bearer </code> prefix and paste the rest into the decoder above; the three dot-separated segments expand into <strong>header</strong> (algorithm + key id), <strong>payload</strong> (claims like <code>sub</code>, <code>exp</code>, <code>scope</code>), and <strong>signature</strong> (verifiable only with the issuer's secret or public key). For inspecting the raw HTTP exchange that produced the token — refresh-token flow, error responses, token-rotation logic — pair the decoder with the <a href="/http-request-builder">HTTP request builder</a>.</p>
<h3>Decode OAuth token vs JWT — when they're the same and when they're not</h3>
<p>An OAuth 2.0 access token <em>can</em> be a JWT, but doesn't have to be. The OAuth spec is silent on token format — issuers choose whether to use opaque random strings (Auth0 by default in some flows, Stripe API keys, GitHub PATs) or self-contained JWTs (Auth0 with an audience claim, Okta, Cognito). If your token starts with <code>eyJ</code>, it's a Base64-encoded JWT — paste it above. If it's an opaque random string (<code>at_2NhwQDdHO…</code> style), it's not a JWT and decoding it client-side returns nothing useful — you must call the issuer's introspection endpoint. The decoder above shows clear "not a JWT" output for opaque tokens so you don't waste time searching for a hidden payload.</p>
<h3>JWT token validator — verifying signatures, audience, expiry</h3>
<p>Decoding shows you the contents; <strong>validation</strong> proves the token wasn't tampered with. The five checks every server should run on every request:</p>
<ol>
<li><strong>Signature.</strong> Verify with the issuer's public key (RS256, ES256) or shared secret (HS256). Never trust a token you couldn't verify.</li>
<li><strong><code>exp</code> (expiry).</strong> Reject tokens past their expiration. Allow ~30 seconds clock skew.</li>
<li><strong><code>iat</code> (issued at) and <code>nbf</code> (not before).</strong> Reject tokens issued in the future or not yet valid.</li>
<li><strong><code>aud</code> (audience).</strong> Reject tokens minted for a different service.</li>
<li><strong><code>iss</code> (issuer).</strong> Reject tokens from an issuer you don't trust.</li>
</ol>
<p>The decoder above runs all five client-side when you provide the public key or secret — useful for debugging "this token works locally but not in prod" issues. To generate test tokens with controlled <code>exp</code>/<code>aud</code>/<code>iss</code> claims, use the <a href="/jwt-generator">JWT generator</a>.</p>
<h3>JWT.io decode alternative — the same workflow without sending tokens off-device</h3>
<p>jwt.io's decoder runs in-browser too, but the URL pattern <code>jwt.io/#token=…</code> means your token sits in browser history (and any tracking pixels that read history). The decoder above never builds a URL with the token in it — paste in the textarea and decoding happens on the keystroke event, no navigation, no URL bar update. For quick spot-checks of a production token, this difference matters; for development tokens, either tool works. The deeper feature parity comparison is in the section below.</p>
<h3>JSON Web Signature (JWS) decode vs JSON Web Encryption (JWE)</h3>
<p>"JSON web signature decode" is a more precise way to say "JWT decode" for tokens that are signed but not encrypted (the typical case). A <strong>JWS</strong> has three Base64URL parts joined by dots; the payload is plaintext JSON anyone can read after Base64 decoding. A <strong>JWE</strong> has five parts and the payload is encrypted — you can see the header but the payload requires the recipient's private key to read. The decoder above handles JWS; for JWE tokens it surfaces the protected header and warns that payload decryption requires the receiver's key (which never belongs in a browser-side tool).</p>
<h2>JWT best practices</h2>
<ul>
<li><strong>Set short <code>exp</code>.</strong> 5–15 minutes for access tokens. Use refresh tokens for longer sessions.</li>
<li><strong>Whitelist algorithms.</strong> Configure your library to accept only one algorithm per verification path. Never trust the <code>alg</code> in the header.</li>
<li><strong>Verify <code>iss</code> and <code>aud</code> always.</strong> These are not optional.</li>
<li><strong>Use HTTPS-only cookies (<code>HttpOnly; Secure; SameSite=Strict</code>) to store JWTs in browsers</strong> if possible — far safer than <code>localStorage</code>, which is vulnerable to XSS.</li>
<li><strong>Rotate signing keys.</strong> Use <code>kid</code> to identify keys. Run a JWKS endpoint that publishes current public keys.</li>
<li><strong>Don't roll your own JWT library.</strong> Cryptography is full of subtle pitfalls. Use <code>jose</code>, <code>PyJWT</code>, <code>jjwt</code>, etc.</li>
<li><strong>Use this decoder for inspection only.</strong> Real verification belongs on a server with a real library and the proper key material.</li>
<li><strong>Don't store JWTs in URLs or query parameters.</strong> They get logged in server access logs, browser history, and analytics. Use <code>Authorization: Bearer</code> headers.</li>
</ul>
</section>
<section class="article-section">
<h2 id="comparison">How this JWT decoder compares to popular alternatives</h2>
<p>"JWT decoder" is one of the most-searched developer-tool queries. The space is dominated by <strong>jwt.io</strong> (Auth0's reference tool, around since 2015), with several modern alternatives competing on different angles. Here's how this tool stacks up so you can pick the right fit for your workflow.</p>
<table class="ref-table">
<thead>
<tr><th>Capability</th><th>FreeDevTool /jwt-decoder</th><th>jwt.io</th><th>token.dev</th><th>jsonwebtoken.io</th></tr>
</thead>
<tbody>
<tr><td>Decode header + payload</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr>
<tr><td>Auto-flag expired tokens (<code>exp</code> in past)</td><td>✅</td><td>✅</td><td>✅</td><td>⚠️ Partial</td></tr>
<tr><td>Verify HMAC signatures (HS256/384/512)</td><td>✅ Web Crypto API</td><td>✅</td><td>✅</td><td>✅</td></tr>
<tr><td>Verify RSA signatures (RS256/384/512)</td><td>Roadmap</td><td>✅</td><td>✅</td><td>✅</td></tr>
<tr><td>Verify ECDSA signatures (ES256/384/512)</td><td>Roadmap</td><td>✅</td><td>⚠️ Partial</td><td>⚠️ Partial</td></tr>
<tr><td>Generate / sign new tokens (paired tool)</td><td>✅ <a href="/jwt-generator">/jwt-generator</a></td><td>✅</td><td>✅</td><td>⚠️ Limited</td></tr>
<tr><td>Browser-only — token never leaves your tab</td><td>✅</td><td>✅</td><td>✅</td><td>⚠️ Unclear in TOS</td></tr>
<tr><td>Open-source / inspectable code</td><td>✅</td><td>✅ GitHub</td><td>⚠️ Closed</td><td>❌</td></tr>
<tr><td>Display ads on the tool page</td><td>❌ Ad-free</td><td>❌</td><td>❌</td><td>✅ Ads present</td></tr>
<tr><td>Paired with long-form RFC 7519 / 9562 implementation guide</td><td>✅ <a href="/guides/api-authentication-guide">26-min read</a></td><td>⚠️ Quick reference</td><td>❌</td><td>❌</td></tr>
<tr><td>Part of a multi-tool dev catalog (Base64, hash, regex, JSON, …)</td><td>✅ 50 tools, 4 in-depth guides</td><td>❌ JWT only</td><td>❌ JWT only</td><td>❌ JWT only</td></tr>
<tr><td>Sign-up required to save / share</td><td>❌ No accounts</td><td>❌</td><td>❌</td><td>❌</td></tr>
</tbody>
</table>
<h3>When to pick which tool</h3>
<ul>
<li><strong>Pick FreeDevTool /jwt-decoder when:</strong> you want a single browser-based JWT decoder paired with a generator (test signing) and an in-depth implementation guide, and you're already using other tools in the FreeDevTool catalog (Base64, hash, regex, JSON formatter) and want a consistent zero-signup workflow.</li>
<li><strong>Pick jwt.io when:</strong> you specifically need RSA / ECDSA signature verification right now, or you want the brand-trust signal of using Auth0's reference tool when sharing debugging output with a teammate or vendor.</li>
<li><strong>Pick token.dev when:</strong> you want a modern, single-purpose JWT debugger with a clean UI focused only on the JWT use case.</li>
</ul>
<p>This is an honest comparison rather than a marketing claim. RSA / ECDSA verification is on the roadmap for FreeDevTool but isn't shipped today; for those algorithms, jwt.io remains the strongest choice. Where FreeDevTool genuinely differentiates is the <strong>tool + paired generator + 26-minute guide + 50-tool ecosystem</strong> combination — useful when JWT debugging is one task in a longer dev workflow, not a one-off lookup.</p>
<h3>Frequently linked competitor pages</h3>
<p>If you're researching JWT decoder alternatives, you're probably also looking at: <a href="https://jwt.io" rel="noopener" target="_blank">jwt.io</a>, <a href="https://token.dev" rel="noopener" target="_blank">token.dev</a>, <a href="https://www.jstoolset.com/jwt" rel="noopener" target="_blank">JsToolSet JWT decoder</a>. Each has trade-offs documented above.</p>
</section>
</article>
<!-- How to use + mistakes -->
<section class="use-cases">
<h2>How to use the JWT decoder</h2>
<p>JWTs are everywhere — OAuth bearer tokens, Auth0/Cognito sessions, signed API requests. The decoder splits a token into its three Base64-URL parts (header, payload, signature) and shows the claims human-readably. Decoding happens entirely in your browser; tokens never leave the page.</p>
<ul class="use-case-list">
<li><strong>1. Paste the JWT</strong> into the input field. Format is <code>header.payload.signature</code>, three Base64URL strings separated by dots.</li>
<li><strong>2. Read the decoded header</strong> to see the algorithm (<code>HS256</code>, <code>RS256</code>, <code>ES256</code>) and key ID (<code>kid</code>) if present.</li>
<li><strong>3. Inspect the payload claims:</strong> <code>iss</code> (issuer), <code>sub</code> (subject), <code>exp</code> (expiration), <code>iat</code> (issued at), <code>aud</code> (audience), plus any custom claims your auth provider added.</li>
<li><strong>4. Check the expiration timestamp.</strong> The decoder converts <code>exp</code>/<code>iat</code>/<code>nbf</code> from Unix seconds to readable local time and flags expired tokens.</li>
<li><strong>5. Copy individual claims</strong> with one click for use in tests, support tickets, or curl commands.</li>
</ul>
<h3>Common JWT mistakes to avoid</h3>
<ul class="mistakes-list">
<li><strong>Trusting the payload without verifying the signature.</strong> The decoder just decodes — it does <em>not</em> verify. Anyone can forge a JWT body. On the server, always verify the signature with the issuer's public key (or HMAC secret).</li>
<li><strong>Storing secrets in JWT payloads.</strong> Bodies are Base64-encoded, not encrypted. Anyone reading the token sees the contents. Use <code>JWE</code> (encrypted JWT) for sensitive claims, or store them server-side.</li>
<li><strong>Forgetting to set <code>exp</code>.</strong> Tokens without expiration live forever — a leaked token is permanent compromise. Set short <code>exp</code> (5–60 minutes) and rotate refresh tokens.</li>
<li><strong>Using <code>alg: none</code> in production.</strong> A 2015 vulnerability — accepting unsigned tokens lets anyone forge any identity. Always whitelist allowed algorithms.</li>
<li><strong>HS256 with a weak secret.</strong> Anything under 32 random bytes is brute-forceable. Use 256-bit random keys, or switch to RS256/ES256 for asymmetric signing.</li>
<li><strong>Not validating <code>iss</code> and <code>aud</code>.</strong> A token signed for service A can be replayed against service B if both share the secret and don't check the audience.</li>
</ul>
</section>
<!-- FAQ -->
<section class="faq-section">
<h2>Frequently Asked Questions</h2>
<div class="faq-item open">
<div class="faq-q" onclick="toggleFaq(this)">
What is the difference between decoding and verifying a JWT?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
<strong>Decoding</strong> a JWT extracts the header and payload by Base64URL-decoding them — no secret key is needed. <strong>Verifying</strong> checks the cryptographic signature against a secret (HMAC) or public key (RSA/ECDSA) to confirm the token hasn't been tampered with. This tool decodes JWTs for inspection purposes. You should always verify signatures on your server before trusting any claims in production, as defined in <a href="https://datatracker.ietf.org/doc/html/rfc7519" style="color:var(--accent)">RFC 7519</a>.
</div>
</div>
<div class="faq-item">
<div class="faq-q" onclick="toggleFaq(this)">
Is it safe to decode a JWT token online?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
It depends on the tool. This JWT decoder runs <strong>100% in your browser</strong> using JavaScript — no data is ever sent to any server. Your token stays on your device. Avoid online decoders that submit tokens to backend APIs, because JWTs often contain user IDs, email addresses, roles, and session data that could be logged or intercepted.
</div>
</div>
<div class="faq-item">
<div class="faq-q" onclick="toggleFaq(this)">
Can you decode a JWT without the secret key?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
Yes. A JWT consists of three Base64URL-encoded parts: header, payload, and signature. The header and payload are <strong>not encrypted</strong> — they are simply encoded. Anyone can decode them without any key. The secret key is only needed to <em>verify</em> the signature. This is why you should never store sensitive data like passwords or credit card numbers directly in a JWT payload.
</div>
</div>
<div class="faq-item">
<div class="faq-q" onclick="toggleFaq(this)">
What does the <code>exp</code> claim mean in a JWT?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
The <code>exp</code> (expiration time) claim is a registered JWT claim defined in RFC 7519. It specifies a UNIX timestamp (seconds since January 1, 1970 UTC) after which the token must not be accepted. If the current time exceeds the <code>exp</code> value, the token is expired. Servers should always check this claim. Common expiration times range from 15 minutes (access tokens) to 7 days (refresh tokens).
</div>
</div>
<div class="faq-item">
<div class="faq-q" onclick="toggleFaq(this)">
Should I store sensitive data in a JWT payload?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
No. JWT payloads are Base64URL-encoded, <strong>not encrypted</strong>. Anyone who intercepts a JWT can read its full payload. Store only non-sensitive identifiers (user ID, role, permissions) in the payload. For truly confidential data, use <strong>JWE</strong> (JSON Web Encryption) defined in RFC 7516, or keep the data server-side and reference it via a claim ID.
</div>
</div>
<div class="faq-item">
<div class="faq-q" onclick="toggleFaq(this)">
What are the registered claims in a JWT?
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<div class="faq-a">
RFC 7519 defines seven registered claims: <code>iss</code> (issuer), <code>sub</code> (subject), <code>aud</code> (audience), <code>exp</code> (expiration time), <code>nbf</code> (not before), <code>iat</code> (issued at), and <code>jti</code> (JWT ID). These are optional but recommended for interoperability. Most OAuth 2.0 and OpenID Connect implementations use these claims.
</div>
</div>
</section>
<!-- Related Tools -->
<section class="related-section">
<h2>Related Tools</h2>
<div class="related-grid">
<a class="related-card" href="base64-encoder">
<div class="related-icon">b64</div>
<div class="related-card-info">
<div class="related-card-name">Base64 Encoder / Decoder</div>
<div class="related-card-desc">Encode and decode Base64 strings</div>
</div>
</a>
<a class="related-card" href="url-encoder">
<div class="related-icon">%</div>
<div class="related-card-info">
<div class="related-card-name">URL Encoder / Decoder</div>
<div class="related-card-desc">Percent-encode and decode URLs</div>
</div>
</a>
<a class="related-card" href="hash-generator">
<div class="related-icon">#</div>
<div class="related-card-info">
<div class="related-card-name">Hash Generator</div>
<div class="related-card-desc">Generate MD5, SHA-256, SHA-512 hashes</div>
</div>
</a>
</div>
</section>
<section class="all-tools-section" aria-label="Browse all FreeDevTool developer tools">
<h2>Browse all 50 free developer tools</h2>
<p class="atc-sub">All tools run in your browser, no signup required, nothing sent to a server.</p>
<div class="all-tools-grid">
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">b64</div>
<div class="atc-cat-title"><h3>Encoding & Conversion</h3><span class="atc-cat-count">11 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/base64-encoder">Base64 Encoder / Decoder</a></li>
<li><a href="/base64-image">Image to Base64</a></li>
<li><a href="/byte-converter">Byte Converter (KB / MB / GB)</a></li>
<li><a href="/case-converter">Case Converter</a></li>
<li><a href="/hex-to-rgb">Hex to RGB / HSL</a></li>
<li><a href="/html-entity">HTML Entity Encoder</a></li>
<li><a href="/json-to-csv">JSON to CSV Converter</a></li>
<li><a href="/px-to-rem">PX to REM Converter</a></li>
<li><a href="/string-escape">String Escape / Unescape</a></li>
<li><a href="/url-encoder">URL Encoder / Decoder</a></li>
<li><a href="/yaml-to-json">YAML to JSON Converter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">{ }</div>
<div class="atc-cat-title"><h3>Formatting & Generators</h3><span class="atc-cat-count">13 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/color-name">Color Name from Hex</a></li>
<li><a href="/color-picker">Color Palette Picker</a></li>
<li><a href="/css-box-shadow">CSS Box Shadow</a></li>
<li><a href="/css-gradient">CSS Gradient Generator</a></li>
<li><a href="/json-formatter">JSON Formatter / Validator</a></li>
<li><a href="/lorem-ipsum">Lorem Ipsum Generator</a></li>
<li><a href="/markdown-preview">Markdown Preview</a></li>
<li><a href="/password-generator">Password Generator</a></li>
<li><a href="/qr-generator">QR Code Generator</a></li>
<li><a href="/sql-formatter">SQL Formatter</a></li>
<li><a href="/uuid-generator">UUID Generator</a></li>
<li><a href="/word-to-markdown">Word to Markdown</a></li>
<li><a href="/xml-formatter">XML Formatter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">JS</div>
<div class="atc-cat-title"><h3>Minifiers & DevOps</h3><span class="atc-cat-count">6 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/chmod-calculator">chmod Calculator</a></li>
<li><a href="/cron-parser">Cron Expression Parser</a></li>
<li><a href="/css-minifier">CSS Minifier</a></li>
<li><a href="/html-minifier">HTML Minifier</a></li>
<li><a href="/js-minifier">JavaScript Minifier</a></li>
<li><a href="/http-status">HTTP Status Codes</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">#</div>
<div class="atc-cat-title"><h3>Security & Hashing</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/hash-generator">Hash Generator (MD5, SHA)</a></li>
<li><a href="/jwt-decoder">JWT Decoder</a></li>
<li><a href="/jwt-generator">JWT Generator</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">.*</div>
<div class="atc-cat-title"><h3>Code & Text</h3><span class="atc-cat-count">8 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/ai-token-counter">AI Token Counter</a></li>
<li><a href="/char-counter">Character & Word Counter</a></li>
<li><a href="/git-cheatsheet">Git Commands Cheatsheet</a></li>
<li><a href="/number-base">Number Base Converter</a></li>
<li><a href="/regex-explainer">Regex Explainer</a></li>
<li><a href="/regex-tester">Regex Tester</a></li>
<li><a href="/text-diff">Text Diff Checker</a></li>
<li><a href="/wcag-contrast">WCAG Contrast Checker</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">IP</div>
<div class="atc-cat-title"><h3>Network & APIs</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/dns-lookup">DNS Lookup</a></li>
<li><a href="/http-request-builder">HTTP Request Builder</a></li>
<li><a href="/ip-lookup">IP Address Lookup</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">⏱</div>
<div class="atc-cat-title"><h3>Time & Dates</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">
<li><a href="/relative-time">Relative Time Calculator</a></li>
<li><a href="/timestamp-diff">Timestamp Diff</a></li>
<li><a href="/unix-timestamp-converter">Unix Timestamp Converter</a></li>
</ul>
</div>
<div class="atc-cat">
<div class="atc-cat-head">
<div class="atc-cat-icon">SEO</div>
<div class="atc-cat-title"><h3>SEO & Meta</h3><span class="atc-cat-count">3 tools</span></div>
</div>
<ul class="atc-list">