If you've worked with any modern authentication system — OAuth, Firebase Auth, Auth0, a custom login flow — you've run into a JWT. It looks like meaningless noise at first glance: three chunks of random-looking text separated by dots. It's not random at all, and you don't need a library to read it.
The three parts
A JWT is always structured as header.payload.signature. Each of the first two parts is a JSON object, Base64URL-encoded into text. The third part is a cryptographic signature that proves the token wasn't tampered with — but critically, it does NOT encrypt the header or payload. Anyone can decode and read them without a secret key.
The header
Usually just two fields: alg (the signing algorithm, commonly HS256 or RS256) and typ (always "JWT"). Not much to see here — it's metadata about the token itself.
The payload — this is where it gets useful
The payload holds "claims" — statements about the user or the token. A few show up constantly:
sub (subject) — usually the user ID the token represents
iat (issued at) — a Unix timestamp of when the token was created
exp (expiration) — a Unix timestamp after which the token is no longer valid; this is why sessions eventually expire
iss (issuer) — who created the token, e.g. your auth provider
Custom claims — anything an app adds, like a role or permission level
Those exp and iat values are raw Unix timestamps, which is exactly the kind of number nobody can read at a glance — pairing a JWT decoder with a timestamp converter turns "1716239022" into an actual date instantly.
The important caveat: decoding isn't verifying
Anyone can decode a JWT's header and payload — no secret required. What you can't do without the signing key is confirm the token is genuine and hasn't been altered. If you're building auth, your backend must verify the signature server-side before trusting anything in the payload. A browser-based decoder is for reading and debugging tokens, never for authenticating a user.
Top comments (0)