Can a JWT be decoded without the secret?

Dark navy cover card with the title 'Can a JWT be decoded without the secret?' in white and a green accent bar

Yes, you can decode a JWT without the secret, as long as it is a normal signed token. Its header and payload are base64url-encoded JSON, and the secret only matters for checking the signature. The exception is an encrypted JWT (a JWE): its claims stay unreadable until you have the decryption key.

Why a signed JWT is readable by anyone

A signed JWT protects against tampering, not against reading. RFC 7515 says JWS "represents content secured with digital signatures or Message Authentication Codes (MACs)" and that its mechanisms "provide integrity protection." Encryption lives in a separate spec.

A typical access token looks like header.payload.signature, three base64url segments joined by two dots. Decode the first two and you get JSON such as {"alg":"HS256","typ":"JWT"} and the claims.

No key is involved in that step, so anyone who holds the token can read sub, email, roles or whatever else the issuer put inside.

What the secret is actually for

The secret, or the issuer's public key, is only needed to verify the signature. RFC 7518 section 3.2 describes the HS256 MAC being generated and then validated "using the shared key", so the same secret both signs and checks.

That splits a JWT's security in two:

  • Reading claims: needs nothing but the token.
  • Trusting claims: needs the key, because a forged token decodes just as cleanly as a real one.

Someone can edit the payload, change "role":"user" to "role":"admin", and re-encode it. Without the key they can't make a signature that matches, and a server that verifies tokens rejects it. A server that only decodes would accept it, which is the real bug to watch for. The guide to decoding a JWT safely covers how to verify properly.

How to decode a JWT without the secret

To decode a JWT without the secret, split it on the dots and base64url-decode the first two segments. In Node it takes two lines:

const [h, p] = token.split(".");
const payload = JSON.parse(Buffer.from(p, "base64url").toString("utf8"));

From a shell, the Base64 command line guide works once you swap - for + and _ for / and add padding.

If you'd rather use a page, our JWT decoder shows the header, payload and signature, plus the iat and exp times with an expired or valid flag. Its tool code makes no network requests. It decodes only: it does not verify the signature, so it can't tell you whether a token is genuine. Before pasting a live token into any website, read whether pasting a JWT into an online debugger is safe.

When you can't read a JWT: encrypted tokens (JWE)

An encrypted JWT can't be decoded without the key. RFC 7519 says JWTs are always represented using either the JWS or the JWE compact serialization, and when the header is for a JWE, "the claims are encrypted."

RFC 7516 says JWE "utilizes authenticated encryption to ensure the confidentiality and integrity of the plaintext." A compact JWE has five parts, in this order:

  1. Protected header
  2. Encrypted key
  3. Initialization vector
  4. Ciphertext
  5. Authentication tag

Only the first part is readable without the key. Decode it and you'll see the alg and enc values, and sometimes a kid naming the key. The ciphertext decodes to random-looking bytes, not JSON.

This is where many explainers go wrong. They say every JWT has exactly two dots and is readable by anyone. That's true of signed tokens, not encrypted ones. RFC 7519 marks support for encrypted JWTs as OPTIONAL, which is why you rarely meet them.

How to tell which kind you have

RFC 7516 section 9 gives two checks, and both give the same answer for valid tokens:

Check Signed JWT (JWS) Encrypted JWT (JWE)
Segments Three, separated by two dots Five, separated by four dots
Header No enc member Has an enc member
Payload without a key Readable JSON Unreadable ciphertext
Key needed to read claims None The decryption key

Counting dots is the fastest test. If token.split(".").length is 5, stop trying to base64-decode the payload; you need the key.

Nested tokens: signed, then encrypted

Some systems do both. RFC 7519 defines a Nested JWT as one where "a JWT is used as the payload or plaintext value of an enclosing JWS or JWE structure." It also says producers should normally sign first and then encrypt, "thus encrypting the signature."

So after you decrypt a JWE, you may find another three-part token inside. Decode that one the normal way, then verify its signature.

What this means for what you put in a token

If your tokens are signed but not encrypted, assume every claim is public to anyone who holds the token. RFC 7519 notes that a JWT "may contain privacy-sensitive information" and suggests encrypted JWTs or TLS-only transport. It also says omitting that information is "the simplest way of minimizing privacy issues."

My take: leave sensitive data out of the token before you reach for JWE. An opaque user ID in sub plus a server-side lookup is simpler to run than key management for encryption. Encrypted tokens make sense when a third party carries the token and must not read it.

The limitation of that advice: a lookup costs a database or cache call on each request, which is the very thing self-contained tokens were meant to avoid.

FAQ

Why can anyone read my JWT payload?

A signed JWT (JWS) only protects integrity. The header and payload are base64url-encoded JSON, so anyone holding the token can decode them. Encryption needs a JWE instead.

How do I tell if a JWT is encrypted?

Count the dots. A signed JWT has three segments and two dots; an encrypted JWE has five segments and four dots. A JWE header also contains an enc member.

Can someone change my JWT if they can read it?

They can edit and re-encode the payload, but they can't produce a valid signature without the signing key. A server that verifies the signature rejects the edited token.

Do I need the secret to check if a JWT is expired?

No. The exp claim sits in the readable payload, so you can check it after decoding. But only a verified token's exp means anything, since a forged token can claim any expiry.

Sources