I’ve reviewed a lot of auth code over the years. The single most common mistake? Treating OAuth and JWT as the same thing, or worse, picking one when you needed the other. They solve different problems, and confusing them leads to real vulnerabilities.
Here’s what each actually does, when to pick which, and how to avoid the traps I’ve seen burn teams in production.
OAuth and JWT Are Not the Same Thing
OAuth is a protocol for delegated authorization. It defines how tokens get issued, exchanged, and revoked when one service needs to act on behalf of a user. Think “Log in with Google” — the user never gives their Google password to your app. OAuth handles the handshake.
JWT (JSON Web Token) is a data format. It’s a signed, self-contained blob of JSON that carries claims — who the user is, what they can do, when the token expires. OAuth can use JWT as its token format, but JWT exists independently of OAuth.
The valet key analogy works: OAuth is the process of getting the valet key from the car owner. JWT is the key itself — compact, verifiable, and self-contained.
Here’s what a JWT payload looks like:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
The sub field identifies the user, admin is a permission claim, and iat is the issue timestamp. The whole thing is signed — tamper with any field and validation fails.
The Real Differences That Matter
Here’s where the confusion gets dangerous:
- Validation model: OAuth tokens are typically validated by calling the authorization server (network round-trip). JWTs are validated locally using the token’s cryptographic signature. This makes JWT validation faster — critical in microservices where every millisecond counts.
- Statefulness: OAuth maintains state on the authorization server (it knows which tokens are active). JWT is stateless — the server doesn’t store anything. This is a strength and a weakness (more on revocation below).
- Scope: OAuth defines the entire authorization flow — redirect URIs, scopes, grant types. JWT just structures and signs data. You can use JWT for things that have nothing to do with OAuth.
In practice, many systems use both: OAuth for the authorization flow, JWT as the token format. But you can also use JWT standalone for stateless session management between your own services.
const jwt = require('jsonwebtoken');
const publicKey = process.env.PUBLIC_KEY;
try {
const decoded = jwt.verify(token, publicKey);
console.log('Token is valid:', decoded);
} catch (err) {
console.error('Invalid token:', err.message);
}
When to Use Which
Pick OAuth when: Third parties are involved. If users need to grant access to external services — social logins, API integrations, “Connect your Slack account” — OAuth provides the framework for safe delegation. You’re not sharing passwords; you’re issuing scoped, revocable tokens.
Pick JWT when: You need lightweight, stateless authentication between your own services. In a microservices setup, passing a signed JWT between services beats hitting a central auth server on every request. It’s faster and removes a single point of failure.
Use both when: You want OAuth for the auth flow but JWT for the actual token. This is the most common production pattern I see — OAuth issues a JWT, and downstream services validate it locally without talking to the auth server.
For example: an e-commerce platform where OAuth authenticates users at the gateway, then JWTs carry user claims to the cart, inventory, and payment services. Each service validates the JWT signature independently. No shared session store needed.
Security Practices That Actually Matter
I’ve seen every one of these mistakes in production code:
Don’t store tokens in localStorage. It’s wide open to XSS attacks. Use secure, HTTP-only cookies. If a script can read it, an attacker’s script can too.
Set short expiration times. A JWT that lives forever is a JWT waiting to be stolen. I default to 15 minutes for access tokens, paired with refresh tokens for extended sessions.
Rotate signing keys. If your signing key is compromised and you’ve been using the same one for two years, every token you’ve ever issued is compromised. Rotate regularly and publish your public keys via a JWKS endpoint.
Don’t put sensitive data in JWT claims. JWTs are signed, not encrypted (by default). Anyone can decode the payload — they just can’t modify it. Never put passwords, credit card numbers, or PII in claims.
Use established libraries. passport.js for OAuth, jsonwebtoken for JWT. These have been battle-tested by thousands of projects. Rolling your own auth is how security vulnerabilities happen.
The JWT Revocation Problem (and How to Solve It)
This is the one thing that trips people up. JWTs are stateless — once issued, the server has no way to “take them back.” If a user logs out or their account is compromised, that JWT is still valid until it expires.
Two approaches that work:
- Token blacklist: Maintain a list of revoked token IDs (
jticlaim) in Redis or similar. Check against it during validation. Yes, this adds state — but only for revoked tokens, not all active ones. - Short-lived tokens + refresh tokens: Keep access tokens short (5-15 min). Use long-lived refresh tokens (stored in HTTP-only cookies) to get new access tokens. When you need to revoke, kill the refresh token. The access token dies naturally within minutes.
I prefer the second approach. It keeps the system mostly stateless while giving you a revocation mechanism that works in practice. The refresh token lives server-side (or in a secure cookie), and revoking it is as simple as deleting it from your store.
Tools and books I’ve actually used or referenced while working on auth systems:
- Zero Trust Networks — Building secure systems in untrusted networks ($40-50)
- YubiKey 5 NFC — Hardware security key for SSH, GPG, and MFA ($45-55)
- The Web Application Hacker’s Handbook — Finding and exploiting security flaws in web apps ($35-45)
- Threat Modeling: Designing for Security — Systematic approach to finding and addressing threats ($35-45)
📧 Get weekly insights on security, trading, and tech. No spam, unsubscribe anytime.

Leave a Reply