Card 08/ 12

GotchaDifficulty: Intermediate1 min

A JWT Is Signed, Not Encrypted

Anyone who holds a JWT can read every claim in it, because base64url is an encoding, not a lock.

A signed JWT protects its claims from being changed, not from being read. Its payload is base64url, which anyone can decode without a key, so every claim is visible to the user, to their browser's extensions, and to any log or proxy the token passes through. Nothing secret belongs in a signed JWT.

The symptom. A security review opens the browser's storage, pastes the token into a decoder, and reads the user's email address, their role and an internal account number.

A token pasted from browser storage and decoded, part by part

InputeyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyXzgzNDEiLCJlbWFpbCI6Im1hcmlhQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzkxMzcwODAwfQ.kH3w9Qe1...

  1. Split on the dots

    1. eyJhbGciOiJSUzI1NiJ9
    2. eyJzdWIiOiJ1c2VyXzgzNDEi...payload
    3. kH3w9Qe1...

    Three parts; no key is needed for this step.

  2. Decode the payload from base64url

    1. sub user_8341
    2. email [email protected]exposed
    3. role admin

    Every claim is plain text, read by anyone holding the token.

  3. Try to change role and send it back

    1. role edited to owner
    2. signature unchanged
    3. server refusesprotected

    The signature no longer matches, so the edit fails. Reading succeeded; changing did not.

ResultThe signature stopped the token being altered, and did nothing to keep its contents private.

Why it happens. The token looks scrambled, and a signature sounds like security, so it is easy to assume both secrecy and integrity.

What it costs. Personal data and internal identifiers exposed wherever the token goes: browser storage, logs, analytics, a support screenshot.

What to do. Put only what the API needs to decide access in the token: a user id, scopes, the standard claims. Look anything else up on the server. If a claim truly must travel secretly, use an encrypted JWT, JWE, defined in RFC 7516, at the cost of every receiver needing the decryption key.