By AndyPublished

JWT vs OAuth: Token Format vs Authorisation Framework

JWT and OAuth are not competing choices, so “JWT vs OAuth” is a category error. OAuth 2.0 (RFC 6749) is an authorisation framework: it defines how a client application obtains permission to call an API, which endpoints hand out tokens and which grant types exist. A JSON Web Token (RFC 7519) is a token format: a compact, signed set of claims. OAuth says a client gets an access token; it deliberately does not say what that token looks like. Many providers make it a JWT, others issue an opaque random string.

So the practical questions are different ones: should your OAuth access tokens be JWTs or opaque, and do you need OAuth at all, or just signed tokens issued by your own login endpoint? This guide answers both, and places SAML and API keys on the same map.

JWT vs OAuth 2.0: Different Layers of the Stack

The easiest way to see the difference is to ask what each specification would say if you removed the other. Remove JWT from OAuth and OAuth still works: the authorisation server issues a random string, stores what it means, and the API asks the server about it. Remove OAuth from JWT and JWT still works: your own login route signs {"sub":"42","exp":...} with a secret and your API verifies it. Neither depends on the other.

AspectJWTOAuth 2.0
What it isA token format: signed (or encrypted) JSON claimsAn authorisation framework: roles, grant types, endpoints
SpecificationRFC 7519, built on JWS (RFC 7515) and JWE (RFC 7516)RFC 6749, with Bearer usage in RFC 6750; OAuth 2.1 is in draft
Answers the questionHow is this set of claims packaged and protected?How does a client get permission to call an API on a user’s behalf?
Moving partsHeader, payload, signatureResource owner, client, authorisation server, resource server
Can exist without the otherYes: session tokens, email links, service-to-service callsYes: many providers issue opaque access tokens
Typical outputeyJhbGciOi... (three dot-separated segments)An access token, optional refresh token, expires_in, scope

OAuth is about delegation: a user lets a third-party client act on their behalf with a limited scope, without handing over a password. JWT is about integrity: whoever receives the token can check that the claims were issued by a known key and have not been changed. The structure of a JWT is covered in detail in what is a JWT.

Is JWT OAuth?

No. A JWT is not OAuth, and using JWTs does not mean you are “doing OAuth”. The confusion comes from the fact that the two are so often seen together:

  • ·Access tokens: Auth0, Okta, Microsoft Entra ID, AWS Cognito and Keycloak issue JWT access tokens (in some cases only when you request a specific API audience or resource). RFC 9068 standardises a JWT profile for them.
  • ·ID tokens: OpenID Connect, the identity layer on top of OAuth 2.0, requires the ID token to be a JWT. This is the one place in the OAuth family where JWT is mandatory.
  • ·Client authentication: RFC 7523 lets a client prove its identity to the token endpoint with a signed JWT instead of a client secret (private_key_jwt in OIDC terms).
  • ·Proof of possession: DPoP (RFC 9449) uses a small signed JWT on each request to bind an access token to a key.

JWT is the preferred serialisation across the OAuth ecosystem, but the framework itself is format-agnostic. For how each of these tokens is used in practice, see JWTs in OAuth 2.0 and OpenID Connect.

How JWT Fits Into an OAuth Flow

Take the authorisation code flow with PKCE, which is the recommended flow for web, mobile and single-page apps. The JWTs appear at specific points:

1. Client  → browser redirect to /authorize?response_type=code&client_id=...
             &scope=openid profile orders:read&code_challenge=...&code_challenge_method=S256
2. User logs in and consents at the authorisation server
3. Server  → redirect back to client with ?code=...
4. Client  → POST /token  grant_type=authorization_code&code=...&code_verifier=...
5. Server  → {
     "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIs...",   ← JWT (or opaque)
     "id_token":     "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...",              ← always a JWT (OIDC)
     "refresh_token":"8xLOxBtZp8",                                    ← usually opaque
     "token_type":   "Bearer",
     "expires_in":   900
   }
6. Client  → GET /orders   Authorization: Bearer <access_token>
7. API validates the access token and applies scope and audience checks

Step 7 is where the format matters. If the access token is a JWT, the API verifies it itself. If it is opaque, the API has to ask the authorisation server. The client should treat the access token as opaque either way: it is meant for the API, and its format can change without notice. The client reads the ID token, not the access token.

⚠
A common bug is sending the ID token to the API. Its aud is the client ID rather than the API identifier, and it carries no scopes. APIs should accept access tokens only. The wrong audience error is the usual symptom.

JWT vs Opaque Token for OAuth2 Access Tokens

This is the real decision hiding behind most “JWT vs OAuth access token” searches. An opaque token is a high-entropy random reference, such as tGzv3JOkF0XG5Qx2TlKWIA, whose meaning lives only in the authorisation server’s database. A JWT access token carries its meaning inside itself.

AspectJWT access tokenOpaque access token
ValidationLocally: signature against the issuer’s JWKS, then iss, aud, expRemotely: call the introspection endpoint (RFC 7662), or look it up in a shared store
Latency per requestNo network call once keys are cachedOne round trip per request unless you cache the result
RevocationNot visible until exp unless you add a deny-list or introspectImmediate: the server simply stops reporting it as active
Who can read the claimsAnyone holding the token (payload is Base64url, not encrypted)Nobody except the authorisation server
SizeHundreds of bytes to several KBUsually 20–80 characters
Best fitMany APIs, microservices, high request volumePublic clients, sensitive claims, instant revocation

JWT access tokens: RFC 9068

RFC 9068 defines a common profile so resource servers can validate JWT access tokens from any compliant issuer. The header uses typ: at+jwt so an access token cannot be confused with an ID token, and the payload must include iss, exp, aud, sub, client_id, iat and jti, with scope when scopes were granted. Validation with the jose library looks like this:

import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));

export async function validateAccessToken(token) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'https://auth.example.com/',
    audience: 'https://api.example.com',
    typ: 'at+jwt',                 // rejects ID tokens (typ "JWT")
    algorithms: ['RS256', 'ES256'],
  });
  const scopes = String(payload.scope ?? '').split(' ');
  return { sub: payload.sub, clientId: payload.client_id, scopes };
}

Not every provider sets typ: at+jwt yet; check a real token before enforcing it. The full check order is in the JWT verification guide.

Opaque tokens: introspection (RFC 7662)

With an opaque token, the API posts it to the authorisation server’s introspection endpoint and receives the metadata back. The API authenticates itself on that call, typically with its own client credentials:

curl -s https://auth.example.com/oauth2/introspect \
  -u "orders-api:API_CLIENT_SECRET" \
  -d "token=tGzv3JOkF0XG5Qx2TlKWIA" \
  -d "token_type_hint=access_token"

{
  "active": true,
  "scope": "orders:read",
  "client_id": "web-app",
  "sub": "user_123",
  "aud": "https://api.example.com",
  "exp": 1790000900
}

active is the only required field in the response. A revoked, expired or unknown token simply returns {"active": false}, which is why opaque tokens give instant revocation. Revoking a token at the server is standardised separately in RFC 7009.

Choosing between them

  • ·Choose JWT when many services validate tokens, request volume is high, and short lifetimes (5 to 15 minutes) are acceptable in place of instant revocation.
  • ·Choose opaque when tokens go to third-party or public clients who should not read the claims, when claims are sensitive, or when revocation must take effect immediately.
  • ·A common hybrid is the “phantom token” pattern: the outside world gets an opaque token, and an API gateway introspects it once and forwards a JWT to internal services.

The revocation trade-off is the same one covered in JWT vs session tokens: self-contained tokens trade freshness for scalability.

JWT vs OAuth: Which Is Better?

Because they solve different problems, the useful question is whether you need OAuth’s delegation machinery.

  • ·You need OAuth (usually with OIDC) when third-party apps call your API on behalf of your users, when you want single sign-on across several applications, when you use an external identity provider, or when you need standard flows for mobile, SPA and machine clients.
  • ·You probably do not need OAuth when one first-party web app talks to its own backend. A server-side session cookie, or a short-lived JWT issued by your own login endpoint, is simpler and has fewer moving parts.
  • ·Do not build your own OAuth server for the sake of it. If you need OAuth, use a maintained authorisation server (a hosted provider, Keycloak, or a certified library).

Whichever you choose, the token rules are the same: pin the algorithm, check iss, aud and exp, keep lifetimes short. See JWT security best practices.

JWT vs OAuth vs SAML

SAML 2.0 is the third name in this comparison, and it overlaps with both. SAML is a complete federation protocol whose tokens (assertions) are XML documents signed with XML Signature. It is built for browser single sign-on into enterprise applications, and it is still widely used for that. OAuth handles API authorisation; OpenID Connect handles modern sign-on and uses JWTs as its token format. Put simply, SAML competes with OpenID Connect, and SAML assertions compete with JWT ID tokens. The full comparison is in JWT vs SAML.

JWT vs OAuth2 vs API Key

An API key is a long-lived static secret that identifies a calling application. It has no expiry, no user context and no standard format. The three fit together like this:

  • ·API key: simplest; good for server-to-server calls by a known customer, usage metering and rate limiting. Revoke by deleting the key.
  • ·JWT: short-lived, signed claims about a user or service; validated without a database lookup.
  • ·OAuth 2.0: the process for issuing tokens (often JWTs) with scopes and user consent. The client credentials grant is the OAuth replacement for a static API key in machine-to-machine traffic.

More detail on where static keys still win is in JWT vs API key.

How to Tell What Kind of Token You Have

If you are handed an access token and are not sure whether it is a JWT, check its shape. Three Base64url segments starting with eyJ is a signed JWT; five segments is an encrypted JWE; no dots at all means an opaque token that only the issuer can interpret. Paste a JWT into the jwtdecode.app decoder, which runs in your browser, to read typ, aud, scope and client_id and see whether it is an access token or an ID token.

Common Misconceptions

  • ·“OAuth is for authentication.” Plain OAuth 2.0 tells an API what a client may do, not who the user is. Logging users in is what OpenID Connect adds, through the ID token and the /userinfo endpoint.
  • ·“A JWT access token is encrypted.” A signed JWT is only encoded; anyone holding it can read the payload. Keep personal data out of it, or use an opaque token. See is a JWT encrypted?
  • ·“Clients should parse the access token.” The access token belongs to the API. A client that reads claims from it breaks when the provider switches formats; clients should read the ID token or call /userinfo.
  • ·“JWTs replace refresh tokens.” Short-lived JWT access tokens usually depend on a refresh token to stay usable, and the refresh token is often opaque and stored server-side. See the refresh token pattern.

Summary

  • ·OAuth 2.0 is an authorisation framework; JWT is a token format. They are complementary, not alternatives.
  • ·OAuth access tokens can be JWTs (profiled by RFC 9068) or opaque strings validated through introspection (RFC 7662).
  • ·OpenID Connect ID tokens are always JWTs, but they are for the client, not the API.
  • ·JWT access tokens scale well and validate locally; opaque tokens revoke instantly and hide their claims.
  • ·Use OAuth when you need delegation, SSO or third-party access; otherwise a session or a simple signed token is often enough.
Ready to decode a token?
Use the free JWT decoder — paste any token for instant results, entirely in your browser.
Open JWT Decoder