What is a JWT, really?
A JSON Web Token is three Base64URL segments joined by dots: header.payload.signature. The first two are plain JSON that anyone can read — that is by design. The third is a cryptographic signature over the first two, which is what actually makes the token trustworthy. The signature is not encryption: a JWT is signed, not secret. Anything you put in the payload is readable by anyone who holds the token.
Common Use Cases
- Debugging an auth failure: "Why is my API rejecting this token?" — decode it and check
exp,nbf,aud, andissbefore touching your code. - Checking expiry:
expandiatare Unix seconds. This decoder converts them to readable local time and flags expired tokens. - Auditing what you leak: Decode a token your own backend issues and confirm it doesn't carry email, roles, or internal IDs you don't want in a URL or log file.
- Reviewing a third-party integration: See exactly which scopes and claims a vendor's token grants.
💡 Pro Tip: the alg: none disaster
The classic JWT vulnerability is a server that reads the alg field the client sent. If it accepts "alg": "none", an attacker strips the signature and forges any payload they like. A second variant: accepting an RS256 token re-signed as HS256 using the public key as the HMAC secret. Both are avoided by pinning the algorithm server-side — never trust alg from the token, and always verify the signature with a real library (jose, PyJWT, jsonwebtoken). Decoding here is for reading, not for verifying.
Frequently Asked Questions
Does this tool verify the signature?
No — and it can't, honestly. Verification requires the signing secret or public key, and a browser tool that asked for your secret would be a security risk in itself. This page only decodes the header and payload. Always verify with a server-side library.
Is it safe to paste a JWT here?
The token is never uploaded — the decoding runs locally in your browser, so nothing goes over the network. That said, a JWT is a bearer credential: anyone who has it can act as that user until it expires. Don't paste production tokens into any online tool you don't control, and revoke/rotate anything you accidentally share.
Why does the payload look like gibberish letters and numbers?
JWTs use Base64URL, a URL-safe variant of Base64 where + becomes - and / becomes _, and padding = is dropped. It exists so tokens can travel in URLs, headers, and cookies without escaping. It is an encoding, not encryption — reversible by anyone.
How do I check whether a token is expired?
Compare the exp claim (Unix seconds) with the current time: Date.now() / 1000 > exp means expired. This decoder shows exp as a readable date and marks it expired. Remember to allow for clock skew — most libraries default to 30-60 seconds of leeway.
The token has only two segments — is it broken?
That's an unsecured JWT (alg: none), which is legal per the spec but almost always a bug or an attack. Treat it as untrusted. If your server accepts it, that's a serious vulnerability — fix it by pinning the accepted algorithms.
How do I decode a JWT in Node.js or Python?
In Node.js 16+, Buffer.from(seg, 'base64url') already understands the URL-safe alphabet, so JSON.parse(Buffer.from(token.split('.')[1], 'base64url')) is the whole job. In Python you must pad the segment first — base64.urlsafe_b64decode(seg + '=' * (-len(seg) % 4)) — because urlsafe_b64decode throws on unpadded input. Both return exactly the JSON shown above, since decoding is only Base64URL plus a JSON parse. Neither one verifies the signature; that requires the key and belongs on your server.
I already get an Entra ID / Auth0 access token — do I still need to mint my own JWT?
Almost never. A hand-rolled second token means two lifetimes, two revocation paths, and a signing key you now have to rotate — while the identity provider already puts the user's sub/oid, scp or roles, and issue/expiry times in the token it gave you. Validate that token on the API (issuer, audience, signature via JWKS), then read the claims instead of re-issuing them. Two things people usually get wrong while doing this: derive authorization from the provider's claim rather than caching a copy of it inside a new token, and keep app-only credentials (client credentials) out of any code path that is supposed to act for a user — those Graph calls lose the user context, so per-user scoping and audit logs stop working.
Which claim identifies the user: sub, oid, or email?
Prefer an opaque, stable id and never key your database on email — it gets reassigned. Beyond that it is provider-specific: in Microsoft Entra ID, sub is a pairwise identifier that differs per application (so it is stable for one app, but not a cross-app user id), while oid is the immutable object id of the user inside the tenant and is the better join key. In Auth0/Okta, sub is usually the stable one (auth0|…, google-oauth2|…). Decode a token from every provider you support and look at the actual claims before writing the lookup — the field name is standard, the semantics are not.