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
| Format | Algorithms | What it provides |
|---|---|---|
| JWS (signed) | HS256, RS256, ES256 | Integrity + 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 →