JWT structure and claims

OAuth, OIDC & JWT · Essentials · 12 min

JWTs carry identity and access information in nearly every OIDC and API deployment.

A JSON Web Token (RFC 7519) is three base64url-encoded parts joined by dots: header.payload.signature.

  • The header names the signing algorithm and usually a key id: {"alg":"RS256","kid":"2025-10"}.
  • The payload is a JSON object of claims.
  • The signature is computed over header.payload with a secret (HMAC, HS256) or a private key (RS256, ES256, EdDSA).

Registered claims you will see everywhere:

  • iss issuer, sub subject (stable user or client id), aud audience (who the token is for).
  • exp expiry, iat issued-at, nbf not-before, all in seconds since the epoch.
  • jti unique token id, useful for replay detection and revocation lists.

Signed JWTs (JWS) are not encrypted. Anyone holding the token can base64-decode the payload and read it, so never put secrets or sensitive personal data inside. Encrypted JWTs (JWE) exist but are less common.

Decoding is not verifying. A token's contents mean nothing until you have checked the signature with a trusted key and validated iss, aud and exp, covered in a later lesson. For debugging, decode locally rather than pasting production tokens into websites.

Example

// Decode (NOT verify) a JWT to see what is inside
const token =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
  "eyJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpIiwiZXhwIjoxOTAwMDAwMDAwfQ." +
  "c2lnbmF0dXJl";
const [header, payload] = token.split(".").slice(0, 2)
  .map((part) => JSON.parse(Buffer.from(part, "base64url").toString("utf8")));

console.log(header);  // { alg: 'HS256', typ: 'JWT' }
console.log(payload); // { sub: 'user-42', aud: 'api', exp: 1900000000 }
console.log(new Date(payload.exp * 1000).toISOString()); // 2030-03-17T17:46:40.000Z
// Anyone can read this. Only a signature check proves who issued it.

Remember

  • header.payload.signature, each base64url-encoded
  • Key claims: iss, sub, aud, exp, iat, nbf, jti
  • Signed is not encrypted: never put secrets in the payload
  • Times are in seconds, not milliseconds

Watch out for

  • Comparing exp with Date.now() (milliseconds) instead of seconds.

Check yourself

  1. What does a valid JWS signature guarantee?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.rfc-editor.org/rfc/rfc7519

Builds on

Nothing: this is a foundation.

Unlocks