Are JWTs Encrypted? Signed vs Encrypted Tokens

Most JWTs are signed, not encrypted. If you Base64-decode the payload, you can read it in plain text. This surprises many developers, so let's clear it up.

JWS — what you almost always use

A standard JWT is technically a JWS (JSON Web Signature). It looks like this:

header.payload.signature

The header and payload are Base64url-encoded — not encrypted. Anyone who has the token can decode and read the claims. The signature only proves the token wasn't tampered with and was issued by the right party.

// Decoding a JWT payload — no secret needed
const [, payload] = token.split('.');
const claims = JSON.parse(atob(payload));

Never put secrets in a signed JWT payload — user IDs, emails, and roles are fine; passwords, SSNs, and API keys are not.

JWE — the encrypted variant

JWE (JSON Web Encryption) is a different format that actually encrypts the payload. A JWE has five parts:

header.encrypted_key.iv.ciphertext.tag

JWE hides the payload from anyone who doesn't have the decryption key. It's less common because:

  • Most systems only need tamper-proof tokens, not secret ones.
  • Encryption adds computational cost and key-management complexity.
  • Transport (HTTPS) already encrypts the token in transit.

Use JWE when you have claims the recipient must not read — for example, tokens passed through untrusted intermediaries.

Algorithm quick reference

FormatAlgorithmsWhat it provides
JWS (signed)HS256, RS256, ES256Integrity + authenticity
JWE (encrypted)RSA-OAEP + AES-GCM, etc.Confidentiality

Summary

  • Signed JWT (JWS) → payload is visible, signature proves authenticity. This is what jwt.sign() produces.
  • Encrypted JWT (JWE) → payload is hidden, only the key holder can read it.
  • When in doubt, keep sensitive data out of the payload entirely.

Inspect a token

Decode any JWT and see its claims instantly with the JWT decoder. It shows you exactly what's in the payload — proving that signing ≠ encryption.

Also see what are JWT claims to understand which fields belong in the payload.

Got a config file to check?

Open the config toolkit →