JWT Payload Best Practices — What to Put (and Not Put) in a Token

The payload of a JWT is only base64-encoded, not encrypted — anyone who holds the token can read every claim. Design the payload around that fact.

What belongs in a JWT

Keep it to what the server needs to authorize a request without a database lookup:

{
  "sub": "user_8f14e45f",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1783468800,
  "iat": 1783382400,
  "roles": ["editor"]
}
  • sub — a stable user identifier (an ID, not an email).
  • iss and aud — who issued the token and who it's for, so a token minted for one API can't be replayed against another.
  • exp and iat — expiry and issue time. Always set exp; short-lived tokens limit the damage of a leak (see JWT expiration and refresh tokens).
  • Coarse authorization data — roles or a tenant ID, if your API checks them on every request.

The standard claim names are covered in detail in what are JWT claims.

What must never go in

Treat the payload as public. Never include:

  • Passwords, password hashes, or security answers
  • API keys, database credentials, or other secrets
  • Sensitive personal data — government IDs, health data, full card numbers

Base64 is an encoding, not encryption. Decoding a token takes one line of code and no key. If you genuinely need confidential claims, that's what JWE (encrypted JWTs) is for — a signed JWS, which is what most people mean by "JWT", hides nothing. More on that in are JWTs encrypted?.

Keep it small

The token rides along on every request, usually in a header. Bloated payloads cost bandwidth and can exceed header size limits (commonly 8 KB) once you stack a few cookies alongside.

  • Store permissions as a compact role ("admin"), not a list of 40 permission strings.
  • Don't embed profile data (name, avatar URL, preferences) — fetch it once from an API instead.
  • If the payload keeps growing, that's a sign state belongs server-side, not in the token.

Don't trust it until you verify it

Reading a claim is safe; acting on one isn't — until you've checked the signature. A client can decode and display exp, but the server must verify before honoring any claim, since anyone can forge an unverified payload. See how to verify a JWT signature.

Inspect your own tokens

The fastest way to audit what your app is actually putting in its tokens: paste one into the JWT decoder and read the payload — everything you see is what an attacker with the token sees. You can also generate test tokens with different claim sets while designing your schema.

Got a config file to check?

Open the config toolkit →