JWT Claims Reference

All registered JWT claim names with type, description and validation rules.

Searchable JWT claims reference covering RFC 7519 registered claims, OpenID Connect ID token and profile claims, plus common access-token claims with their types and validation rules. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What are the standard registered JWT claims?

RFC 7519 defines seven registered claims: iss (issuer), sub (subject), aud (audience), exp (expiration), nbf (not before), iat (issued at) and jti (JWT ID). They are optional but reserved with defined meanings, so do not repurpose them.

JWT claims reference

A JSON Web Token carries its data as claims — name/value pairs inside the JSON payload. Some claim names are registered by RFC 7519 with precise meanings (iss, exp, aud), others are standardized by OpenID Connect for identity (email, nonce, auth_time), and the rest are public or private to your application. Verifying a token is mostly about validating these claims correctly: the wrong skew on exp, a skipped aud check, or trusting an unverified email are all real-world security holes. Search the reference above by claim name or keyword.

How it works

JWT claims live in the second segment (the payload) of a header.payload.signature token, base64url-encoded JSON. After verifying the signature with the issuer’s key, a validator must check the claims: exp and nbf are NumericDate integers (seconds since the Unix epoch) bounding the validity window; iss must match a trusted issuer; aud must include your service; and jti can be tracked to block replay. OpenID Connect layers identity claims on top — nonce ties an ID token to a specific auth request, auth_time supports max_age, and azp names the authorized party. Access tokens (RFC 9068) commonly add scope, client_id, and roles for authorization decisions.

Tips and examples

  • Always validate the trio that prevents misuse: signature, exp/nbf (with small skew), and aud. Skipping any one undermines the others.
  • Use sub as the stable user key, never email or preferred_username, which can change or collide.
  • For replay-sensitive flows, persist seen jti values until their exp so a token cannot be used twice.
  • Gate identity trust on booleans: rely on email_verified before linking accounts, and check azp equals your client_id when aud has multiple values.

Validation order that matters

When a resource server receives a token, the correct order of checks is:

  1. Parse the header to extract alg and optionally kid.
  2. Reject dangerous algorithms — never accept alg: none or symmetric HMAC when you expect asymmetric keys.
  3. Fetch the public key from the issuer’s JWKS endpoint using kid if present.
  4. Verify the signature over the exact bytes of the original token.
  5. Check exp — reject if current time is past it (allow a small clock skew, typically 30–60 seconds).
  6. Check nbf — reject if current time is before it.
  7. Check iss — must match the expected issuer URI exactly.
  8. Check aud — must contain your service’s identifier.

Skipping step 8 is the most common mistake in practice. A token legitimately issued for one API can then be replayed against a different API on the same platform. The aud claim is specifically there to prevent this.

Custom and private claims

Beyond registered and OIDC claims, applications often add their own claims — tenant_id, plan, feature_flags, org_id, and so on. The IANA guidance is to namespace these with a URI to avoid future collisions with registered names: for example "https://example.com/roles": ["admin"] rather than a bare "roles". Bare short names like role, permissions, and groups are popular in practice but risk clashing with future standardised claims. Use the URI prefix for any claims you plan to publish or share across systems.

NumericDate pitfalls

exp, nbf, iat, and auth_time use NumericDate: seconds (not milliseconds) since the Unix epoch. The most frequent bug is passing Date.now() directly — JavaScript’s Date.now() returns milliseconds, producing a value 1,000 times too large. The token appears to expire in the year ~50000 and is never rejected. Always divide by 1,000 when setting time claims from JavaScript: Math.floor(Date.now() / 1000).