How to decode a JWT (read claims) without verifying the signature
Real snippets to extract JWT header/payload (Base64URL) and read claims. Useful for debugging; not a replacement for verification.
Use this article when...
This section is built for speed: one concrete task, multiple stacks, and the minimum context needed not to copy the wrong thing.
Work from the request or response body that actually failed instead of rebuilding JSON from memory.
The useful path here is fast adaptation: choose your stack, then copy only after reading the notes around parsing and formatting.
Once the snippet runs, inspect the live payload with JSON Formatter or the related tool to confirm the structure is really fixed.
What this article gives you
A short path from incident to implementation: context, language-specific snippets, and the warning signs that usually get skipped.
13
3
jwt, auth, security, debugging
Decoding a JWT means “reading it”: split header.payload.signature, Base64URL-decode it, and parse JSON. This helps you inspect exp/nbf, iss, aud, scopes/roles, and custom claims.
Important: decoding does NOT validate the signature. If a token comes from a client, never trust its payload without verification on the server.
Dependency snapshot by language
Check this first to see which snippets use built-in/runtime APIs and which ones need external packages before you start copying code.
Node.js.
Built-in or runtime API
Review the notes for setup details.
Built-in or runtime API
Built-in or runtime API
Built-in or runtime API
Built-in or runtime API
Review the notes for setup details.
Built-in or runtime API
Built-in or runtime API
Review the notes for setup details.
Built-in or runtime API
Built-in or runtime API
How to use these code examples well
The snippet is only the fast path. The explanation and notes are what keep you from copying the wrong assumption into production.
Make sure the article really matches the issue you are debugging before copying code just because the title looks close.
That is where portability limits, dependency requirements, and runtime assumptions usually appear.
If the code runs but the bug remains, jump to the related tool and inspect the real payload, token, timestamp, or config.
Code examples by language
Snippets that compile/run, plus short explanations and real-world notes.
Bash
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
TOKEN="$TOKEN" node -e "const t=process.env.TOKEN; const [h,p]=t.split("."); console.log(Buffer.from(h,"base64url").toString("utf8")); console.log(Buffer.from(p,"base64url").toString("utf8"));"
Uses Node.js to reliably Base64URL-decode from the terminal and print header and payload as JSON.
- Dependency: Node.js.
- This does not verify signature; it is read-only for debugging.
C#
using System;
using System.Text;
using System.Text.Json;
static byte[] Base64UrlDecode(string input) {
var s = input.Replace('-', '+').Replace('_', '/');
switch (s.Length % 4) {
case 2: s += "=="; break;
case 3: s += "="; break;
}
return Convert.FromBase64String(s);
}
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
var parts = token.Split('.');
var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(parts[0]));
var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(parts[1]));
using var header = JsonDocument.Parse(headerJson);
using var payload = JsonDocument.Parse(payloadJson);
Console.WriteLine(JsonSerializer.Serialize(header, new JsonSerializerOptions { WriteIndented = true }));
Console.WriteLine(JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
Converts Base64URL → bytes, decodes UTF-8, and parses JSON. Useful to inspect claims quickly while debugging.
- System.Text.Json (std).
- Do not use this as security validation: verification is missing.
Delphi
program JwtDecode;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.NetEncoding;
function Base64UrlToBase64(const S: string): string;
var
T: string;
begin
T := StringReplace(S, "-", "+", [rfReplaceAll]);
T := StringReplace(T, "_", "/", [rfReplaceAll]);
while (Length(T) mod 4) <> 0 do
T := T + "=";
Result := T;
end;
var
Token: string;
Parts: TArray<string>;
PayloadJson: string;
begin
Token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
Parts := Token.Split([Char(46)]);
PayloadJson := TNetEncoding.Base64.Decode(Base64UrlToBase64(Parts[1]));
Writeln(PayloadJson);
end.
In Delphi, you convert Base64URL to Base64 (replacements + padding) and decode using TNetEncoding.Base64. The result is the payload JSON ready to inspect.
- Std: System.NetEncoding.
- No signature verification; debugging only.
Go
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
func main() {
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
parts := strings.Split(token, ".")
payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
panic(err)
}
var v any
if err := json.Unmarshal(payloadBytes, &v); err != nil {
panic(err)
}
pretty, _ := json.MarshalIndent(v, "", " ")
fmt.Println(string(pretty))
}
RawURLEncoding handles Base64URL without padding. Then parse JSON and print it indented.
- Std.
- For verification you need a JWT library + keys (and validate exp/iss/aud).
Java
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
String payload = token.split("\\.")[1];
byte[] decoded = Base64.getUrlDecoder().decode(payload);
System.out.println(new String(decoded, StandardCharsets.UTF_8));
}
}
Base64.getUrlDecoder() decodes Base64URL. Printing the payload JSON is useful to inspect claims quickly.
- Std.
- To parse JSON in Java you typically use Jackson/Gson (dependencies).
JavaScript
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
const payload = token.split(".")[1];
const json = Buffer.from(payload, "base64url").toString("utf8");
console.log(JSON.stringify(JSON.parse(json), null, 2));
In Node.js, Buffer supports base64url. Decode, parse JSON, and print it formatted.
- Node.js.
- No verification: inspection only.
Kotlin
import java.nio.charset.StandardCharsets
import java.util.Base64
fun main() {
val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
val payload = token.split(".")[1]
val decoded = Base64.getUrlDecoder().decode(payload)
println(String(decoded, StandardCharsets.UTF_8))
}
Kotlin/JVM uses java.util.Base64 URL-safe decoder to get the payload JSON.
- Std.
- For pretty print/parsing, use a JSON library (dependency).
PHP
<?php
$token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
$payload = explode(".", $token)[1];
$payload = strtr($payload, "-_", "+/");
$payload .= str_repeat("=", (4 - strlen($payload) % 4) % 4);
$json = base64_decode($payload, true);
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
echo json_encode($data, JSON_PRETTY_PRINT) . PHP_EOL;
Normalize Base64URL to Base64 (replacements + padding), decode and parse JSON. Great to inspect claims.
- JSON_THROW_ON_ERROR (PHP 7.3+).
- Decode ≠ verify: do not treat this as security.
Python
import base64
import json
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
payload_b64 = token.split(".")[1]
raw = base64.urlsafe_b64decode(payload_b64 + "==")
obj = json.loads(raw.decode("utf-8"))
print(json.dumps(obj, indent=2, ensure_ascii=False))
Base64URL is decoded with urlsafe_b64decode (adding padding if needed). Then parse the payload JSON.
- Std.
- If you need verification, use a JWT library and validate exp/iss/aud.
Ruby
require "base64"
require "json"
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
payload = token.split(".")[1]
json = Base64.urlsafe_decode64(payload)
obj = JSON.parse(json)
puts JSON.pretty_generate(obj)
Base64.urlsafe_decode64 handles Base64URL. Then JSON.parse and pretty_generate to print readable claims.
- Std.
- No signature verification.
Rust
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use serde_json::Value;
fn main() {
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
let payload = token.split(".").nth(1).unwrap();
let bytes = URL_SAFE_NO_PAD.decode(payload).unwrap();
let v: Value = serde_json::from_slice(&bytes).unwrap();
println!("{}", serde_json::to_string_pretty(&v).unwrap());
}
The URL_SAFE_NO_PAD engine decodes Base64URL without padding. Then parse JSON and print it nicely.
- Dependencies: base64 = "0.22", serde_json = "1".
TypeScript
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature";
const payload = token.split(".")[1];
const json = Buffer.from(payload, "base64url").toString("utf8");
console.log(JSON.stringify(JSON.parse(json) as unknown, null, 2));
In Node.js, Buffer supports base64url. This is the most direct way to read payloads in TS scripts.
- Node.js.
- Decode ≠ verify.
VB.NET
Imports System
Imports System.Text
Imports System.Text.Json
Module Program
Private Function Base64UrlDecode(input As String) As Byte()
Dim s = input.Replace("-", "+").Replace("_", "/")
Select Case (s.Length Mod 4)
Case 2 : s &= "=="
Case 3 : s &= "="
End Select
Return Convert.FromBase64String(s)
End Function
Sub Main()
Dim token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE5MDAwMDAwMDB9.signature"
Dim parts = token.Split("."c)
Dim payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(parts(1)))
Using doc = JsonDocument.Parse(payloadJson)
Console.WriteLine(JsonSerializer.Serialize(doc, New JsonSerializerOptions With {.WriteIndented = True}))
End Using
End Sub
End Module
Converts Base64URL to Base64 (replacements + padding), decodes UTF-8, and parses JSON to print readable claims.
- System.Text.Json (std).
- No signature verification.
Related tools
Use the tool when you need to generate or validate output fast.
Trust and privacy
Copy/paste-friendly by design. Still, treat real production secrets carefully.
- Prefer synthetic examples when sharing screens or recording.
- Do not paste real API keys, tokens, or personal data unless you fully trust your environment.
- If you need ordering, do not use UUID v4 as a sortable id: add createdAt or use sortable ids.