A JWT token, or JSON Web Token, is a compact, signed token that carries a set of claims about a user or client, so a service can trust those claims without calling back to whoever issued them. It has three parts, a header, a payload, and a signature, joined by dots, and the signature is what makes it trustworthy. JWTs are defined in RFC 7519 and are the usual choice for OAuth access tokens; OpenID Connect ID tokens are always JWTs by standard.
The reason a JWT token is everywhere is that it's self-contained. A service that receives one validates the signature and reads the claims locally, with no database lookup, which is what makes JWTs fast and easy to scale. That is a double-edged sword: the same self-containment that makes a JWT fast also makes it hard to revoke a single token before it expires. This guide covers the structure, the claims inside, how validation works, when to reach for a JWT versus an opaque token, and the mistakes that keep showing up in postmortems.
A JWT is three base64url-encoded strings joined by dots: header.payload.signature. The header states how the token is signed. The payload holds the claims. The signature is computed over the first two parts and lets a recipient confirm the token came from the stated issuer and has not been altered.
Decoded, the first two parts are plain JSON.
The header:
{
"alg": "RS256",
"typ": "JWT"
}
The payload:
{
"iss": "https://issuer.example.com",
"sub": "user_123",
"aud": "https://api.example.com",
"exp": 1767225600,
"iat": 1767222000,
"scope": "billing.read"
}
Encoded, the token becomes one long string, eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOi... and so on, ending in the signature. Base64url is encoding, not encryption, so the header and payload are readable by anyone holding the token. That fact drives half the security rules later in this post.
Claims are the statements inside a JWT, and they fall into three groups. Registered claims are standard reserved names with defined meanings. Public and private claims are the ones you add for your own use. Registered claims do most of the work in practice.
The registered claims you will see most often are iss (issuer, who minted the token), sub (subject, who the token is about), aud (audience, which service the token is for), exp (expiry time), iat (issued-at time), nbf (not valid before), and jti (a unique token ID). An access token usually adds a scope claim; an OpenID Connect ID token adds profile claims such as email or name. Keep payloads small, because every claim rides on every request.
A JWT is signed one of two ways. Symmetric signing, such as HS256, uses one shared secret to both sign and verify, which suits a single party doing both. Asymmetric signing, such as RS256 or ES256, uses a private key to sign and a public key to verify, which is what you want when many services need to verify tokens an identity provider issued, because the verifiers only need the public key.
Validation is more than checking the signature, and treating it as one step is where breaches start. A correct check verifies the signature against the issuer's key, then confirms the token has not expired (exp), is past its not-before time (nbf), was minted by the expected issuer (iss), and is meant for this service (aud). A perfectly valid signature on a token issued for a different audience should still be rejected. The signing keys themselves come from the issuer's JWKS endpoint and rotate over time, so verifiers fetch and cache the current public keys rather than pinning one forever.
Use a JWT when speed and independence matter, and an opaque token when revocation matters. A JWT is self-contained, so any service can validate it locally without a network call, which is ideal for high-volume APIs and stateless services. An opaque token is a random string with no readable content, validated by asking the issuer, which makes it easy to cancel centrally but adds a lookup on every use.
The trade-off is real and worth stating plainly: a JWT stays valid until it expires, even if you want it dead sooner, because no service is checking with the issuer. An opaque reference token can be killed the instant you revoke it. Many teams split the difference by using short-lived JWTs for ordinary calls and reference tokens for sensitive ones. Token introspection, defined in RFC 7662, is the standard mechanism for validating opaque tokens against the issuer.
Four JWT mistakes come up again and again, and each one has ended up in a security writeup. They are all avoidable with a vetted library and a few fixed rules.
First, putting secrets in the payload. A JWT is encoded, not encrypted, so anyone holding it reads the payload; never place passwords, card numbers, or sensitive personal data in a plain JWT. Second, trusting the token's own algorithm header. Accepting whatever alg the token claims, including the infamous alg: none, lets an attacker forge tokens, so always pin the expected algorithm on the verifier. Third, checking only the signature and skipping exp, iss, and aud. Fourth, hand-parsing tokens instead of using a maintained library, because the subtle bugs live in the parsing. If you take one habit from this post, make it validating the algorithm and the claims, not just the signature.
MonoCloud is an OIDC-certified identity platform that issues standards-based JWT access tokens and OpenID Connect ID tokens, and it also supports opaque reference tokens for cases where central revocation matters more than self-containment. You choose the format per API through an access policy, so one API can hand out self-contained JWTs for low-risk calls and revocable reference tokens for sensitive ones without changing your API code.
That control lives in MonoCloud's API Access Policies, which are written in or compiled to Cedar and evaluated before any access token is issued. A policy can set the token format, shorten the token lifetime for a riskier context, and bind a token to the user's session so it stops working the moment the session ends. If you want the authorization side of this in depth, read What Are Cedar Policies?. For a full login built on these tokens, see the Next.js Authentication guide, or start building on MonoCloud for free.