Skip to content

Analisar o payload de um JWT snippet

Um JWT são três partes base64url separadas por pontos: header.payload.signature.

Um JWT são três partes base64url separadas por pontos: header.payload.signature. Ler o payload é mecânica pura — cortar no ponto, descodificar base64url a parte do meio, analisar JSON. As duas armadilhas: o alfabeto base64 é URL-safe (- e _, não + e /) com o padding removido, pelo que a maioria dos descodificadores precisa de ambos corrigidos antes de o aceitar; e ANALISAR NÃO É VERIFICAR — o payload de um token não verificado é um saco de afirmações que qualquer um pode escrever. Não confie em nada até a assinatura estar verificada do lado do servidor.

Receita executável · 12 linguagensAbrir a ferramenta jwt →
HTTP & APIsjwttokenbase64urljsonauth

Every language

12 linguagens, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
function parseJwt(token) {
  const part = token.split('.')[1]; // payload
  let b64 = part.replace(/-/g, '+').replace(/_/g, '/');
  b64 += '='.repeat((4 - (b64.length % 4)) % 4); // restore stripped padding

  return JSON.parse(atob(b64));
}

atob yields Latin-1 — claims with non-ASCII names need the TextDecoder route shown in TypeScript. Reading claims client-side is fine; trusting them is not.

TSTypeScript
interface Claims {
  sub: string;
  exp: number;
  [key: string]: unknown;
}

function parseJwt(token: string): Claims {
  const part = token.split('.')[1];
  const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
  const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);

  const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
  return JSON.parse(new TextDecoder().decode(bytes)); // UTF-8 safe
}

Uint8Array.from(atob(...)) is the byte bridge that keeps non-ASCII claims intact. Check exp against Date.now() / 1000 after parsing.

GoGo
func parseJwt(token string) (map[string]any, error) {
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		return nil, errors.New("jwt: want 3 dot-separated parts")
	}

	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, err
	}

	var claims map[string]any
	return claims, json.Unmarshal(payload, &claims)
}

RawURLEncoding is EXACTLY the JWT alphabet — unpadded URL-safe — so Go needs none of the string surgery other languages do. golang-jwt verifies.

RsRust
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use serde_json::Value;

fn parse_jwt(token: &str) -> Option<Value> {
    let payload = token.split('.').nth(1)?;
    let bytes = URL_SAFE_NO_PAD.decode(payload).ok()?;
    serde_json::from_slice(&bytes).ok()
}

URL_SAFE_NO_PAD matches the JWT spec byte-for-byte. The jsonwebtoken crate does sign and verify; this only reads.

PHPPHP
function parseJwt(string $token): array {
    $payload = explode('.', $token)[1];

    $b64 = strtr($payload, '-_', '+/'); // base64url → base64 alphabet
    $b64 .= str_repeat('=', (4 - strlen($b64) % 4) % 4);

    return json_decode(
        base64_decode($b64),
        true,
        flags: JSON_THROW_ON_ERROR,
    );
}

strtr swaps the URL-safe alphabet in one call. firebase/php-jwt is the verify-with-a-real-library answer.

PyPython
import base64
import json

def parse_jwt(token: str) -> dict:
    payload = token.split('.')[1]
    padded = payload + '=' * (-len(payload) % 4)  # pad to multiple of 4
    return json.loads(base64.urlsafe_b64decode(padded))

-len % 4 is the concise pad-to-multiple-of-4 (0 stays 0). urlsafe_b64decode expects the padded form even though JWT omits it.

C#C#
using System.Buffers.Text;
using System.Text.Json;

static JsonDocument ParseJwt(string token) =>
    JsonDocument.Parse(
        Base64Url.DecodeFromChars(token.Split('.')[1]));

.NET 9's Base64Url kills the whole alphabet-and-padding dance in one call. Before it: Replace('-', '+'), Replace('_', '/'), pad, Convert.FromBase64String.

JvJava
String payload = token.split("\\.")[1];

// TRAP: getUrlDecoder REQUIRES padding — JWT strips it, put it back
String padded = payload + "=".repeat((4 - payload.length() % 4) % 4);

byte[] json = Base64.getUrlDecoder().decode(padded);
Map<String, Object> claims =
        new ObjectMapper().readValue(json, Map.class); // Jackson

Java's getUrlDecoder is the one mainstream decoder that rejects unpadded input — the padding restore is mandatory, not optional.

SwSwift
import Foundation

func parseJwt(_ token: String) throws -> [String: Any] {
    let payload = token.split(separator: ".")[1]

    var b64 = payload
        .replacingOccurrences(of: "-", with: "+")
        .replacingOccurrences(of: "_", with: "/")
    while b64.count % 4 != 0 { b64 += "=" }

    guard let data = Data(base64Encoded: b64) else {
        throw URLError(.badServerResponse)
    }
    return try JSONSerialization.jsonObject(with: data) as! [String: Any]
}

Store tokens in the Keychain, never UserDefaults. JWTKit / JWTDecoder for verification — this only reads.

KtKotlin
import java.util.Base64
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject

fun parseJwt(token: String) = Json.parseToJsonElement(
    String(
        Base64.getUrlDecoder().decode(
            token.split('.')[1]
                .let { it + "=".repeat((4 - it.length % 4) % 4) },
        ),
    ),
).jsonObject

Same JVM padding trap as Java. The jsonObject result reads like a map: claims["sub"] — kotlinx.serialization keeps it allocation-light.

RbRuby
require 'base64'
require 'json'

def parse_jwt(token)
  payload = token.split('.')[1]

  b64 = payload.tr('-_', '+/')
  b64 += '=' * ((4 - b64.length % 4) % 4)

  JSON.parse(Base64.strict_decode64(b64))
end

String#tr swaps alphabets in place. For rails-size code, jwt gem — decode(token, key, true) verifies while parsing.

ZigZig
const std = @import("std");

pub fn main() !void {
    const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.sig";

    var it = std.mem.splitScalar(u8, token, '.');
    _ = it.next(); // header
    const payload = it.next() orelse return error.BadToken;

    const dec = std.base64.url_safe_no_pad.Decoder;
    const len = try dec.calcSizeForSlice(payload);
    const buf = try std.heap.page_allocator.alloc(u8, len);
    defer std.heap.page_allocator.free(buf);
    try dec.decode(buf, payload);

    var parsed = try std.json.parseFromSlice(
        std.json.Value, std.heap.page_allocator, buf, .{});
    defer parsed.deinit();

    std.debug.print("{s}\n", .{parsed.value.object.get("sub").?.string});
}

std.base64.url_safe_no_pad is the exact JWT alphabet — no surgery needed, like Go. parseFromSlice returns an owned value; deinit() it.

Keep going

Try the interactive jwt tool →