"Should we use JWT or OAuth?" is a category error, and APIs designed around it end up with home-grown login tokens, ID tokens accepted as access tokens and no way to revoke anything. JWT (RFC 7519) is a format for carrying signed or encrypted claims. OAuth 2.0 (RFC 6749) is a framework for a client to obtain limited, delegated access to an API. They operate at different layers, they are often used together, and each is also used without the other. This guide is for API architects, developers and security reviewers who need to choose a design, review one, or explain to a team why "we use JWT" does not answer how an API is authorised. Token format trade-offs and deep JWT hardening are covered in the JWT vs Opaque Tokens and JWT Security Guide pages; this one is about where the two meet and where they are confused.
Key takeaways
- Different layers, not alternatives. JWT defines what a token looks like; OAuth defines who issues tokens to whom, for which resource and with what consent. An OAuth access token may be a JWT, or may not.
- Neither one is authentication. A valid JWT proves that an issuer signed some claims; OAuth proves a client was granted access. User authentication for clients comes from OpenID Connect, whose ID Token is a JWT with strict validation rules.
- RFC 9068 is the bridge. When OAuth access tokens are JWTs, RFC 9068 fixes the header type (at+jwt), seven required claims and the validation steps a resource server must run.
- Most real failures are confusion failures. ID tokens used to call APIs, tokens for one audience accepted by another, and session JWTs with no expiry all come from treating "a signed JWT" as sufficient.
- API keys still fit some jobs. Simple server-to-server metering and low-risk public data can justify keys; delegated user access, third-party clients and fine-grained scopes need OAuth.
The short answer
Use OAuth 2.0 (with OpenID Connect where clients need to know who the user is) to decide how clients obtain tokens. Then decide the access token format: a JWT validated locally under RFC 9068, or an opaque reference validated by introspection. Use JWTs outside OAuth only where a standard profile defines them, such as OAuth client authentication assertions (RFC 7523) or OpenID Connect ID Tokens, and avoid inventing your own JWT login scheme. If someone asks "JWT or OAuth?", the useful answer is "OAuth for the flow, and here is why we chose this token format inside it".
Category confusion: what each standard actually defines
| Standard | What it is | What it defines | What it leaves out |
|---|---|---|---|
| JWT, RFC 7519 | Token format | A compact, URL-safe way to represent claims, encoded as a JWS (signed or MACed) or JWE (encrypted); seven registered claims (iss, sub, aud, exp, nbf, iat, jti), all optional | Which claims are required, who issues tokens, how they are obtained, how they are revoked. RFC 7519 says the claims an application needs are outside its scope |
| OAuth 2.0, RFC 6749 | Authorisation framework | Roles (resource owner, client, authorisation server, resource server), grants, token endpoint, scopes | The access token format. Section 1.4 says access tokens can have different formats, structures and methods of utilisation |
| Bearer usage, RFC 6750 | Token presentation | Authorization: Bearer header, error codes invalid_token (401) and insufficient_scope (403) | Token contents |
| JWT access token profile, RFC 9068 | OAuth profile for JWT access tokens | typ at+jwt, required claims iss, exp, aud, sub, client_id, iat, jti, resource server validation steps | Whether you must use JWTs at all: OAuth still mandates no format |
| OpenID Connect Core 1.0 | Identity layer on OAuth 2.0 | The openid scope, the ID Token (a JWT about the authentication event), UserInfo | API authorisation decisions |
| JWT BCP, RFC 8725 (BCP 225) | Implementation guidance | Threats and practices for anyone producing or consuming JWTs | Flows and grants |
The confusion has a cause. Many identity providers issue JWT access tokens by default, and many tutorials show a login endpoint that returns a JWT. Teams then conclude that JWT is a login method. It is not: the OWASP API Security Top 10 2023 entry API2 Broken Authentication states plainly that OAuth is not authentication, and neither are API keys, and it lists accepting unsigned JWTs ({"alg":"none"}) and not validating expiry as signs of a broken API.
Where JWTs appear in and around OAuth
| Token or assertion | Defined by | Who creates it | Who validates it | Key validation point |
|---|---|---|---|---|
| JWT access token | RFC 9068 | Authorisation server | Resource server (API) | typ at+jwt, aud contains the API's identifier, iss exact match |
| ID Token | OpenID Connect Core | OpenID Provider | Client (relying party) only | aud contains the client's client_id; nonce matches when sent |
| Client assertion | RFC 7523 (client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer) | OAuth client | Authorisation server | sub equals client_id, aud identifies the authorisation server, short exp |
| JWT authorisation grant | RFC 7523 (grant type urn:ietf:params:oauth:grant-type:jwt-bearer) | A trusted issuer or the client | Authorisation server | Trusted issuer, audience, expiry, replay policy |
| Self-issued session JWT | No OAuth standard | Your own login service | Your own APIs | Whatever you wrote: this is where most bespoke flaws live |
The table matters because each row has a different intended audience. RFC 8725 calls the misuse of a token issued for one purpose in another context cross-JWT confusion, and recommends explicit typing and mutually exclusive validation rules (distinct typ values, claims, keys, audiences or issuers) so that one kind of JWT can never pass validation as another. For client assertions in depth, see the JWT Client Authentication (private_key_jwt) Guide; for the cross-app variant of the JWT grant, see the Identity Assertion JWT Authorization Grant (Cross-App Access) Guide.
Is JWT authentication?
No. Verifying a JWT signature authenticates the token's issuer and integrity, not the person or workload presenting it. Most JWT access tokens are bearer tokens: RFC 6750 describes a bearer token as usable by any party in possession of it, without proof of a key. So "the request carried a valid JWT" means "someone holds a token the issuer signed", which is why sender constraint (DPoP or mutual TLS) exists. See the DPoP Guide.
What authenticates the user to a client is OpenID Connect. Its core specification calls it a simple identity layer on top of OAuth 2.0: the client requests the openid scope and receives an ID Token, a JWT whose aud must contain the client's own client_id. That last rule is the practical line between authentication and authorisation. An ID Token tells the client who signed in; it is not addressed to your API and must not be sent to one as an access token. The relationship is covered further in OAuth 2.0 vs OIDC.
Two threat models: OAuth without JWT, and JWT without OAuth
OAuth with opaque access tokens
- Architecture in words: the authorisation server issues a random reference; the API (or its gateway) asks the introspection endpoint what the token means and caches the answer briefly.
- Threat model: token theft and replay, introspection endpoint abuse or outage, over-broad scopes granted at consent time.
- Failure modes: APIs that skip introspection under load, caches that outlive revocation, an introspection endpoint reachable without client authentication.
- Controls: authenticated introspection, cache lifetimes set per API risk, audience-restricted tokens, short lifetimes with refresh token rotation. See the OAuth Token Introspection and Revocation Guide.
JWT used without OAuth
- Architecture in words: a login endpoint checks a password and returns a JWT signed with a shared secret; every API validates it with the same secret and trusts its claims.
- Threat model: forged tokens through weak HMAC secrets or algorithm confusion, tokens that never expire, no revocation, no audience, no client identity, no consent for third-party access.
- Failure modes: RFC 8725 lists weak symmetric keys open to offline brute force and RSA-to-HMAC algorithm confusion among JWT threats; a single shared HMAC secret also means every API that can verify tokens can mint them.
- Controls: if you must, use asymmetric signing, an algorithm allowlist, explicit typ, aud per API, short exp and a server-side session or deny list for logout. In most cases a standard session cookie for first-party web apps, or an OAuth authorisation server, is simpler and safer. See Session Cookies vs Bearer Tokens.
OAuth with JWT access tokens (RFC 9068)
- Architecture in words: the authorisation server signs access tokens with keys published as a JWKS; APIs validate locally without a network call per request.
- Threat model: signing key compromise, validation shortcuts in API code, claims disclosure to clients, revocation lag until exp.
- Controls: the RFC 9068 section 4 checks, RS256 support (mandatory under the profile) or stronger asymmetric algorithms, key rotation, short lifetimes. See the JSON Web Key Sets (JWKS) and Signing Key Rotation Guide.
Validation pitfalls where JWT and OAuth meet
| Pitfall | What goes wrong | Control | Source |
|---|---|---|---|
| ID Token accepted as an access token | A client sends its ID Token to the API; the API checks only the signature and issuer, both valid | Require typ at+jwt and reject other values; require aud to contain the API identifier, not a client_id | RFC 9068 s2.1 and s4; OIDC Core |
| Missing audience check | A token issued for API A is replayed at API B by anyone who obtained it (substitution) | Each API checks aud contains its own identifier | RFC 8725; RFC 9068 s4 |
| Algorithm taken from the token | alg none accepted, or an RSA public key used as an HMAC secret | Library configured with an explicit algorithm allowlist; one algorithm per key | RFC 8725 s3.1; RFC 9068 s4 |
| Untrusted key location | jku or x5u headers point the validator at attacker keys; kid used unsanitised in a lookup | Resolve keys only from the configured issuer's JWKS; allowlist key URLs; sanitise kid | RFC 8725 |
| Scope ignored | Any valid token for the API can call any endpoint | Map scopes to routes; return insufficient_scope (403) | RFC 6750 s3 |
| Client reading the access token | Client logic depends on claims and breaks when the format changes, or leaks them | Treat access tokens as opaque in clients; get user data from the ID Token or UserInfo | RFC 9068 s6 |
| No expiry or very long expiry | Stolen tokens work for days or forever | exp required and checked; lifetimes in minutes for JWT access tokens | RFC 9068 s2.2; RFC 6750 s5.3; OWASP API2:2023 |
Specific error handling and troubleshooting for these checks are in the JWT Validation Errors Troubleshooting Guide.
When API keys still fit
API keys are a third option that teams weigh against "JWT or OAuth", and sometimes they are the right one. A key identifies a calling application or account; it carries no user, no delegated consent and usually no expiry unless you add one.
| Situation | API key | OAuth access token (JWT or opaque) |
|---|---|---|
| Metering a server-side integration to low-risk or public data | Fits, with rate limits, IP or network restrictions and rotation | Works but may be heavier than needed |
| Third-party app acting for your users | Does not fit: no user consent, no per-user scope | Fits: authorisation code with PKCE, scopes, revocable grants |
| Machine-to-machine within your organisation | Acceptable short term with a secrets manager | Better: client credentials with private_key_jwt or mTLS, short-lived tokens. See the Machine-to-Machine OAuth Guide |
| Fine-grained, per-endpoint permissions | Possible only with bespoke key scoping | Native through scopes and audiences |
| Fast revocation after compromise | Rotate the key, which breaks every caller using it | Revoke the grant or refresh token for one client or user |
A related anti-pattern is wrapping an API key in a JWT: signing a long-lived key into a token with no exp gains nothing. Migration paths and hardened key patterns are in the API Keys vs OAuth Tokens Guide.
Worked scenario: reviewing "we use JWT"
A product team presents an API design: a mobile app posts a username and password to /login, receives a JWT signed with HS256 using a secret held in every microservice, and sends it on each request. The token has sub and role claims and an exp of 30 days. A partner now wants to read customer orders on behalf of users.
- Classify: this is JWT without OAuth. There is no client identity, no audience, no consent model, and a shared secret means any compromised service can mint admin tokens.
- Introduce an authorisation server: the app uses the authorisation code flow with PKCE (a native app pattern, see the OAuth Native Apps Guide); the partner registers as a separate client and gets user consent for an orders:read scope.
- Choose the access token format: internal APIs get RFC 9068 JWTs signed with an asymmetric key, 10-minute exp, aud per API; the partner-facing gateway introspects and forwards an internal token, so the partner never sees claims.
- Separate identity: the app reads the user's profile from the ID Token or UserInfo, not from the access token.
- Retire the old tokens: run both validators in parallel, stop issuing legacy tokens, then reject any token without typ at+jwt after the longest legacy exp has passed.
Long-lived login JWTs also widen the blast radius of unrelated leaks: in the Wyndham hardcoded app password breach, the exposed database held customer login tokens, and whether they could still be used depended on whether they had been invalidated.
Decision guidance
- First-party web app, same domain: a server-side session cookie is often simplest. If you use OAuth, keep tokens in a backend for frontend rather than the browser. See the OAuth Browser-Based Apps and BFF Guide.
- Mobile or single-page app calling your APIs: OAuth with PKCE and short-lived access tokens; format per the JWT vs opaque decision.
- Third parties or AI agents acting for users: OAuth is required for consent, scoping and revocation; JWT alone cannot express any of these.
- Service to service: OAuth client credentials with JWT client assertions or mTLS, or workload identity federation; avoid shared HMAC secrets.
- Small organisations: use a managed identity provider and its default token format, a maintained validation library and short lifetimes. Do not write a JWT login service.
- Large organisations: publish one token validation profile (algorithms, issuers, audiences, typ, lifetimes) and enforce it at gateways and in shared libraries; see API Gateway vs In-Service Token Validation.
How to implement
- Inventory token types: list every JWT and token your estate issues or accepts, with issuer, audience, typ, algorithm and lifetime. Flag self-issued session JWTs and any API that accepts ID Tokens.
- Write the validation profile: allowed algorithms, issuer per environment, audience per API, required typ at+jwt for access tokens, maximum lifetime and clock skew.
- Enforce in audit mode: log, but do not reject, tokens that fail the new checks for one to two weeks; review which clients send ID Tokens or tokens for the wrong audience.
- Fix clients, then enforce: move clients to proper access tokens and scopes, then switch the checks to reject with invalid_token.
- Replace bespoke login JWTs: migrate to an authorisation server and OIDC, or to server-side sessions for first-party web apps.
- Roll back safely: keep the enforcement switch per API and per check so one failing integration does not force removing all validation.
Detection and measurement
- Log validation failures by reason: wrong typ, wrong aud, unknown kid, expired, bad signature, with client_id and a token hash, never the token. The OWASP Logging Cheat Sheet lists JWT validation problems among events to log.
- Alert on: any token with alg none or an algorithm outside the allowlist; ID Tokens presented to APIs; tokens from unexpected issuers.
- Metric: profile coverage: share of APIs enforcing aud and typ checks. Target 100% for externally reachable APIs.
- Metric: maximum access token lifetime across issuers. Target minutes, not hours.
- Metric: self-issued JWT schemes remaining: count, trending to zero.
- Metric: long-lived API keys on endpoints that serve user data, trending to zero.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | IA-5 Authenticator Management | Token and key lifetimes, signing key protection, API key rotation |
| NIST SP 800-53 Rev 5 | IA-9 Service Identification and Authentication | Client authentication with JWT assertions or mTLS |
| NIST SP 800-53 Rev 5 | AC-3 Access Enforcement | Scope and audience checks at the API |
| NIST SP 800-53 Rev 5 | SC-23 Session Authenticity; SC-8 Transmission Confidentiality and Integrity | Token integrity and TLS for bearer tokens |
| OWASP API Security Top 10 2023 | API2:2023 Broken Authentication | Unsigned or unexpired JWTs, OAuth treated as authentication |
| MITRE ATT&CK v20 | T1528 Steal Application Access Token; T1550.001 Application Access Token; T1606 Forge Web Credentials | Token theft and replay; forged tokens when signing keys or validation fail |
Common failure patterns
- "We use JWT" as the security design: no issuer separation, audience or scope model, so any valid token works everywhere.
- ID Tokens sent to APIs: a single-page app forwards the ID Token because it was easier to get, and the API accepts it because the signature is valid.
- Shared HMAC secret across services: every verifier can mint tokens; one leaked configuration file means forged admin tokens.
- Thirty-day login JWTs with no revocation: logout and password reset do nothing until exp.
- Clients parsing access tokens: features break when the identity provider changes format, and claims leak to devices. RFC 9068 says the client must not inspect the access token.
- API keys standing in for delegated access: a partner integration given a key with full account access instead of a user-consented, scoped OAuth grant.
Practitioner checklist
- Describe every API's design as "OAuth flow plus token format", never "JWT".
- Require typ at+jwt, exact iss and per-API aud for JWT access tokens.
- Configure an explicit algorithm allowlist; reject alg none.
- Resolve signing keys only from the configured issuer's JWKS.
- Reject ID Tokens at every API.
- Keep JWT access token lifetimes in minutes and use refresh token rotation.
- Replace self-issued login JWTs with OIDC or server-side sessions.
- Use RFC 7523 client assertions or mTLS instead of shared client secrets for machine clients.
- Limit API keys to low-risk, server-side use with rotation and network restrictions.
- Log validation failures by reason without logging tokens.
Standards and references
- IETF: RFC 7519 JSON Web Token (JWT)
- IETF: RFC 6749 The OAuth 2.0 Authorization Framework
- IETF: RFC 9068 JWT Profile for OAuth 2.0 Access Tokens
- IETF: RFC 8725 JSON Web Token Best Current Practices
- IETF: RFC 6750 OAuth 2.0 Bearer Token Usage
- IETF: RFC 7523 JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- OpenID Foundation: OpenID Connect Core 1.0
- OWASP: API2:2023 Broken Authentication
Related NHI Mgmt Group resources: JWT Security Guide · JWT vs Opaque Tokens · OAuth 2.0 and OpenID Connect Guide · API Keys vs OAuth Tokens Guide · API Security Guide