Decode Your JWT's Standard Claims: iss, sub, aud, exp
Paste any token into the decoder above and it separates the seven registered claims from everything else the payload carries. RFC 7519 specifies iss, sub, aud, exp, nbf, iat, and jti as the reserved claim set that every compliant JWT library understands.1 These claims appear across every provider, including Auth0, Okta, Azure AD, and Keycloak, with identical semantics regardless of the signer.
Beyond the registered set, providers add private claims: any key outside the RFC 7519 list. AWS Cognito uses cognito:groups, Keycloak uses realm_access, Supabase uses role. Because no collision protection exists for private claims, the RFC recommends namespacing them as URIs or reverse-domain strings, for example https://yourapp.com/roles rather than a bare roles key.1 The decoder above displays both registered and private claims in full, so you can identify which fields are standard and which are provider-specific before you configure validation safely in code.
Identity claims: iss and sub
iss (issuer) identifies who created the token, typically as a URL: for example, https://accounts.google.com or https://yourapp.auth0.com/. Verifying iss prevents cross-issuer token acceptance: an Auth0 token for one tenant must not authenticate against a different tenant's API. sub (subject) identifies the entity the token represents: almost always a user or service ID that is stable and unique within the issuer's namespace.
Using sub as a stable account key
Consequently, sub is the correct primary key for a user database record.2 Never use email as a primary key; email addresses change while sub values remain constant. When a user updates their email address, links a new social account, or switches from username-password to SSO, the sub stays the same, which means your database foreign keys, audit logs, and session records all remain valid without requiring a migration or update cascade across related tables.
Audience and time claims: aud, exp, nbf, iat
aud (audience) identifies who the token is for: your API's identifier or client ID.2 Verifying aud prevents token misuse across services: a token issued for your analytics API must not authenticate against your billing API, which is a common vulnerability in systems that accept any valid token regardless of the intended recipient. exp (expiration) is a Unix timestamp in seconds after which the server must reject the token. nbf (not before) is the earliest timestamp at which the token is valid. iat (issued at) records the issuance time, enabling your server to calculate the token's age and reject tokens that were issued unreasonably far in the past.
Building on this, the three time claims together let you define a precise validity window: issued at iat, valid from nbf, expiring at exp, and understanding this window is essential for correctly configuring the clock skew tolerance in your JWT verification middleware so that minor clock differences between your servers and the token issuer do not cause spurious authentication failures.
Replay prevention: jti
jti (JWT ID) is a unique string identifier for the token that enables replay attack prevention by giving your server a way to recognize tokens it has already processed.3 Without jti, a stolen token remains valid until exp, meaning any service that captures the token can use it for the entire remaining lifetime. With jti, your server stores used jti values in a short-lived cache keyed by jti and expiring at exp, rejecting any token whose jti has already been seen. Yet jti alone is insufficient because you still need signature verification and exp checking to close all the main attack surfaces for token-based authentication systems handling sensitive operations, so always validate all three mechanisms together.
Configuring claim validation in JWT libraries
Configuring claim validation means setting expected values for iss, aud, exp, and nbf in your library at startup, not checking claims manually after decoding.4 In PyJWT, pass audience='https://api.yourapp.com' and issuer='https://yourprovider.com' to jwt.decode(): the library raises specific exceptions for each mismatch. In jose (JavaScript), pass audience and issuer options to jwtVerify(). Both libraries treat validation parameters as server-side configuration, not values derived from the incoming token.
Avoiding manual claim checking after decode
Avoid the pattern of decoding without validation and then writing manual if-statement reject checks on claim values. Manual checks are easy to omit, hard to audit, and silently absent during refactors. Library-level validation parameters run on every decode call unconditionally. For aud validation where your API accepts multiple values, pass an array to the audience option: most libraries perform an intersection check and accept the token when at least one value matches.
Validation parameters as trusted configuration
When adding custom claims, treat validation parameters as trusted server configuration. The incoming token can claim any iss or aud value, but your library should compare those values against constants loaded from environment variables or deployment settings. This approach keeps validation decisions out of request data and makes security reviews easier because the expected values live in one auditable location.
Loading expected values from configuration also makes environments easy to clone because the same code runs everywhere with different constants. A reviewer can see every accepted issuer and audience in one place rather than hunting through request handlers, and all registered JWT claims explained shows every registered and private claim a token actually carries, which is the fastest way to confirm what your validation config should check for before you write it. This centralisation reduces the chance that a quick fix introduces an over-broad check that accepts tokens it should reject.
When to use this
Check a decoded token against these claim definitions when configuring JWT validation parameters, or when implementing custom claims that need to coexist with the registered set without naming conflicts.
Examples
A minimal valid JWT payload with all seven registered claims
{
"iss": "https://auth.yourapp.com/",
"sub": "user_abc123",
"aud": "https://api.yourapp.com/",
"exp": 1748736000,
"nbf": 1748732400,
"iat": 1748732400,
"jti": "unique-token-id-abc123xyz"
} In practice, most providers omit nbf and jti. Only iss, sub, aud, exp, and iat appear in most production tokens.
Namespaced private claims alongside registered claims
{
"iss": "https://auth.yourapp.com/",
"sub": "user_abc123",
"exp": 1748736000,
"https://yourapp.com/roles": ["admin", "editor"],
"https://yourapp.com/tier": "enterprise"
} Using a URL namespace for private claims prevents collisions if the JWT spec adds a new registered claim with the same name.
- 1.
M. Jones, J. Bradley, and N. Sakimura, "JSON Web Token (JWT)," RFC 7519, IETF, May 2015. https://www.rfc-editor.org/rfc/rfc7519
- 2.
J. Bradley, M. Jones, and H. Tschofenig, "JSON Web Token Best Current Practices," RFC 8725, IETF, February 2020. https://datatracker.ietf.org/doc/html/rfc8725
- 3.
IANA, "JSON Web Token Claims," iana.org, accessed June 2026. https://www.iana.org/assignments/jwt/jwt.xhtml
- 4.
Auth0, "Validate JSON Web Tokens," auth0.com, accessed June 2026. https://auth0.com/docs/secure/tokens/json-web-tokens/validate-json-web-tokens