skip to content

Why must you never put a secret in a JWT payload even though the token is signed?

level: juniorimportance: must knowfreq 74%

answer

  1. One property is provided, one is not
  2. No key is needed to read it
  3. Tokens end up in logs and history
  4. Carry an identifier, not the data
  5. Encryption is a separate token type

basics

~20 s

Signing protects integrity, not confidentiality. A JWS payload is only Base64url-encoded, so anyone holding the token — or reading a log, a proxy trace or browser storage — can decode every claim. Secrets belong outside the token or inside an encrypted one.

solid answer

~50 s

A signed JWT (a JWS) guarantees that nobody altered the claims without detection. It guarantees nothing about who can *read* them: the payload is Base64url, an encoding with no key, so decoding is a copy-paste operation. That matters because tokens spread further than people expect — access logs, proxy and CDN traces, browser local storage, crash reports, analytics, error messages, and query strings when someone puts a token in a URL. Anything you place in the payload should be treated as published to everyone in that chain. Keep the payload to identifiers and coarse authorization facts, and hold sensitive attributes server-side behind the identifier. If a claim genuinely must travel confidentially, encrypt it — that is what JWE exists for — but the usual right answer is to not put it in the token at all. Payload size is a second reason: every claim is resent on every request.

code

json · 8 lines
json
{
  "iss": "https://auth.example.com/",
  "sub": "user-1042",
  "aud": "orders-api",
  "scope": "orders:read",
  "iat": 1764496400,
  "exp": 1764497300
}

go deeper

for a junior

Be able to state that a signed JWT's payload is only encoded, not encrypted, and that anyone holding the token can read every claim. Never place secrets or personal data there.

for a middle

Explain the split precisely — signature gives integrity and origin, not confidentiality — and name the routes by which tokens leak: logs, query strings, browser storage, crash reports, proxies.

for a senior

Show judgment about claim design: minimal opaque identifiers, scopes actually enforced, sensitive attributes resolved server-side, and awareness that token size is paid on every request.

for a principal

Own the policy: a documented allowlist of claims for the organisation, privacy review for anything personal in a token, and encryption reserved for cases where a claim genuinely must travel confidentially.

## Signed is not sealed A JSON Web Signature provides **integrity** and **origin authentication**: the recipient can prove the claims came from the key holder and have not been modified. It provides no **confidentiality**. The three segments of a compact JWS are Base64url encodings, and Base64url is a transport encoding — it exists to make bytes safe for URLs and headers, not to hide them. Decoding needs no key, no permission, and no tooling beyond a browser console. This surprises people because a JWT *looks* like ciphertext: a long opaque-seeming string of random-looking characters. That visual impression is the whole reason the mistake keeps happening. ## Who actually reads your payload The attacker who steals a token is the obvious reader, but the routine leaks matter more because they happen every day without an incident: - **The client itself.** A browser or mobile app holds the token; its user can read it. - **Access logs.** Tokens in an `Authorization` header are usually not logged, but tokens in query strings are logged by nearly every web server, proxy and CDN — and they land in browser history and `Referer` headers too. - **Intermediaries.** Reverse proxies, service meshes, API gateways and observability agents frequently capture headers for debugging. - **Error reporting.** Crash and exception reporters serialize request context, including headers, and ship it to a third-party service. - **Support workflows.** "Paste your token so I can look at it" is a real support script, and the person pasting it may not know what is inside. Every one of those is a place your claims end up in plaintext. ## What belongs in the payload Aim for the minimum that lets a verifier make a decision: - An identifier for the principal (`sub`) — a stable, opaque one, not an email address or a national identifier. - Issuer and audience (`iss`, `aud`) and the time claims (`iat`, `nbf`, `exp`). - A token identifier (`jti`) when you need per-token handling. - Coarse authorization facts: scopes or roles the verifier must enforce. ## What does not belong - Passwords, API keys, database credentials, or any secret of any kind — including "temporary" ones. - Personal data beyond what the verifier needs: full name, home address, date of birth, government identifiers, health or financial attributes. - Internal system detail an attacker would find useful: internal hostnames, database identifiers, feature-flag internals, or a description of your permission model beyond the scopes actually being enforced. A useful test: *would you be comfortable if this JSON appeared verbatim in a public log?* If not, it does not go in the payload. ## Privacy regulation makes this concrete Personal data in a token is personal data you have distributed to every system that touches the request, including third-party observability vendors. That expands the scope of a data-protection assessment considerably, and it is not something you can retract — tokens already issued keep circulating until they expire. Minimising the payload is a privacy control as much as a security one. ## When you genuinely need confidential claims JOSE has an answer: JSON Web Encryption produces a token whose payload is ciphertext, readable only by the holder of the decryption key. It costs key management for a second key pair or shared key, and it hides the claims from your own debugging as well as from attackers. Before reaching for it, ask whether the claim needs to travel at all — usually the alternative is simply to keep the data server-side and put an identifier in the token, which is cheaper and leaks nothing. ## Size is the quieter cost A JWT is sent on every request, typically in a header. Stuffing profile attributes, long permission lists or arbitrary application state into the payload inflates every single request, and header-size limits at proxies and servers produce confusing failures when tokens grow past a few kilobytes. Small tokens are both safer and faster. ## Interview framing Answer in one line — *signed means unmodified, not unreadable* — then show that you know where tokens leak, name the minimal claim set you would carry, and mention encryption as the specialised tool rather than the default. A candidate who says "it's encrypted because it looks like gibberish" has failed the question; a candidate who says "assume the payload is public" has passed it.

  • If claims must stay confidential, what changes about the token?
    You use JSON Web Encryption instead of, or wrapped around, a signed token, so the payload is ciphertext that only a holder of the decryption key can read. That means managing a second key relationship and losing easy debuggability. Most teams avoid it by keeping sensitive attributes server-side and carrying only an opaque identifier in the token.
  • Is putting a JWT in a URL query string acceptable if the connection uses TLS?
    No. TLS protects the token in transit but not at the endpoints: query strings are written to server and proxy access logs, kept in browser history, and historically forwarded in `Referer` headers to third-party sites. Send tokens in an `Authorization` header, or in a cookie with the appropriate protections, so they stay out of URL-shaped storage.
  • How do you decide which claims deserve a place in the payload?
    Include only what a verifier needs to make its decision without a lookup — subject identifier, issuer, audience, time claims, and the scopes actually enforced. Everything else stays server-side behind the subject identifier. Apply the public-log test: if seeing the JSON in a log would be a problem, it does not belong in the token.

saying these in an interview costs you the question

  • Says the payload is encrypted because it looks random
  • Puts an email address or full profile in the claims
  • Assumes only the server can decode the token
  • Sends the token in a URL query parameter
  • Thinks a strong signing key protects payload confidentiality

context