Decoding JSON Web Tokens (JWTs) Safely Without Leaking Secrets
By Huzaifa • Published: 2026-10-07 • 6 min read
Understand the three Base64URL segments of an RFC 7519 JSON Web Token, why decoding is not signature verification, and why live bearer tokens belong only in local client-side tools.
When debugging single sign-on (SSO), OAuth 2.0 / OpenID Connect flows, or stateless API authentication, developers regularly need to inspect what is inside an Authorization: Bearer <token> header. Most of the time, that compact string separated by two dots is a JSON Web Token (JWT) defined by RFC 7519 (specifically a JWS—JSON Web Signature).
Because a signed JWT looks like an opaque wall of random characters, two dangerous misconceptions crop up frequently: first, assuming the payload inside a JWT is encrypted and hidden from the client; and second, pasting active production bearer tokens into random server-backed websites to read their claims.
Anatomy of a Signed JWT: Three Base64URL Parts
Every standard JWS compact serialization consists of three dot-separated segments:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOTQyIiwicm9sZSI6ImVkaXRvciIsImV4cCI6MTc5MzQ1NjAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|____________ Header _____________|.|____________________ Payload ____________________|.|_______________ Signature _______________|
- JOSE Header (Part 1): A JSON object encoded with Base64URL describing the cryptographic signing algorithm (
alg, such asHS256,RS256, orES256), the token type (typ: "JWT"), and optionally a key identifier (kid) telling the server which public key from its JWKS endpoint should verify the token. - Claims Payload (Part 2): A JSON object encoded with Base64URL containing standard registered claims and custom application claims.
- Cryptographic Signature (Part 3): Computed over the exact ASCII bytes
base64UrlEncode(header) + "." + base64UrlEncode(payload)using either a shared HMAC secret (HS256) or an asymmetric private key (RS256,ES256,EdDSA).
Base64URL vs. Standard Base64
Notice that JWTs use Base64URL (RFC 4648 §5), not standard Base64. Standard Base64 uses +, /, and = padding characters, all of which have special meanings inside URL query parameters and HTTP headers. Base64URL replaces + with -, replaces / with _, and strips trailing = padding so the token is safe inside URLs without percent-encoding. You can experiment with general Base64 strings in the Base64 Encoder & Decoder, or inspect complete tokens directly in the JWT Decoder.
Signed (JWS) Does NOT Mean Encrypted (JWE)
Anyone who possesses a signed JWT—including the user's browser, mobile app, or any script with access to it—can Base64URL-decode the header and payload in one line of code without knowing the secret key:
function decodeJwtPayload(token) {
const parts = token.split('.');
if (parts.length !== 3) throw new Error('Invalid JWT format');
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
}
What the signature guarantees is integrity and authenticity, not confidentiality: if a client modifies "role": "viewer" to "role": "admin" in the payload and re-encodes it, the signature in Part 3 will no longer match when the backend verifies it with its secret or public key.
Because the payload is readable in plain JSON:
- Never store passwords, database credentials, API keys, or sensitive personal data (such as tax IDs or unmasked payment details) inside a JWS payload. If confidential claims must travel inside a token, use JWE (JSON Web Encryption, RFC 7516) or keep sensitive state server-side behind an opaque session ID.
Registered Claims Every Backend Must Validate
Decoding a token during debugging is helpful for checking why an API returned 401 Unauthorized or 403 Forbidden. On the server, however, decoding (jwt.decode()) must never be confused with verifying (jwt.verify()). A backend verifier must check both the cryptographic signature and the token's temporal and audience claims:
exp(Expiration Time): A Unix epoch timestamp in seconds (not milliseconds!) after which the token must be rejected. When inspecting rawexpintegers manually, paste them into the Timestamp Converter to compare against current UTC time.nbf(Not Before) &iat(Issued At): Unix timestamps in seconds indicating when the token becomes valid and when it was minted. Allow a small clock-skew tolerance (such as 30–60 seconds) between microservices, not hours.iss(Issuer): Identifies which authorization server issued the token (for example,https://accounts.example.com).aud(Audience): Crucial in multi-service architectures! If Service A and Service B trust the same identity provider, Service B must verify thataudexplicitly includes Service B—otherwise a token minted for a low-privilege service could be replayed against an administrative API.sub(Subject): The stable unique identifier of the user or service principal.
Classic JWT Implementation Pitfalls to Avoid
- Algorithm confusion and
alg: "none": Never let the untrustedalgheader inside an incoming token dictate how your backend verifies it! Always pass an explicit allowlist of expected algorithms (for instance,algorithms: ['RS256']) to your JWT verification library. Otherwise, legacy vulnerabilities allowed attackers to set"alg": "none"with an empty signature, or trick a server expecting anRS256public key into using that public PEM string as anHS256symmetric HMAC secret. - Weak
HS256secrets: SymmetricHS256tokens can be brute-forced offline just like unsalted hashes if signed with a short dictionary secret like"secret123". Always use at least 256 bits (32 random bytes) from a CSPRNG forHS256keys. - Long-lived unrevocable access tokens: Because stateless JWTs remain valid until
exp, keep access token lifetimes short (5 to 15 minutes) and pair them with rotatable refresh tokens stored inHttpOnly; Secure; SameSitecookies when revocation is needed.
Keep Bearer Tokens Out of Remote Third-Party Logs
A live bearer JWT is literally a temporary master key to the account it represents: anyone who has the string can impersonate that session until exp arrives. Never paste live production tokens into websites that submit inputs to a backend API, into public issue trackers, or into unredacted chat channels.
When you inspect a token in the ToolTux JWT Decoder, decoding happens entirely inside your local browser memory via standard JavaScript—no network request is sent. Even better, use expired staging tokens or sample test tokens whenever you are sharing examples with teammates.
Featured Tools Mentioned in This Guide
- JWT Decoder — Decode JSON Web Tokens (JWT) header and payload claims without sending secrets to any server.
- Base64 Encoder & Decoder — Encode plain text or decode Base64 strings safely with live validation and error reporting.
- Timestamp Converter — Convert Unix epoch timestamps to human-readable UTC and local dates instantly.