---
title: "OAuth vs JWT: A Format vs a Flow (and Why You Probably Use Both)"
description: "OAuth vs JWT is the wrong comparison — they're not even the same kind of thing. A JWT is a token FORMAT: a tamper-proof, self-contained ID card the server verifies by signature, no database lookup. OAuth is a delegation PROTOCOL: how an app gets permission to access your data in another app without ever seeing your password. Here's how they actually differ — with code — and why an OAuth flow usually hands back a token that's a JWT."
keywords: "OAuth vs JWT, JWT, OAuth, OAuth 2.0, JSON Web Token, authentication, authorization, access token, bearer token, OpenID Connect, stateless authentication, Log in with Google, refresh token, session vs JWT, API security, backend system design"
created_at: "2026-06-27T11:00:00"
post_type: "anonym_post"
content_type: "technical_article"
---

![Banner](./banner.webp)

<div style="display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; padding: 1rem 1.25rem; margin: 1.5rem 0 2rem; background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border: 1px solid #e2e8f0; border-radius: 12px;">
  <span style="font-size: 0.95rem; font-weight: 600; color: #475569; margin-right: 0.25rem;">Prefer to watch?</span>
  <a href="https://youtube.com/shorts/6-k_Uqkx2U8" target="_blank" rel="noopener noreferrer" style="display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.55rem 1rem; border-radius: 8px; background: #ff0000; color: #ffffff; font-size: 0.875rem; font-weight: 600; text-decoration: none;">▶ OAuth vs JWT in 90 seconds</a>
  <a href="https://t.me/SoftwareEngineerBlog" target="_blank" rel="noopener noreferrer" style="display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.55rem 1rem; border-radius: 8px; background: #229ed9; color: #ffffff; font-size: 0.875rem; font-weight: 600; text-decoration: none;">✈ Telegram</a>
</div>

"Should I use OAuth or JWT?" gets asked in interviews, on Stack Overflow, and in design reviews constantly — and it's a bit of a trick question. The honest answer is that **they're not competing options.** One is a *kind of token*. The other is a *way to hand out tokens*. You can use either without the other, and most real systems use both at once.

So before you pick, get the categories straight:

- **JWT** is a token **FORMAT** — a specific, self-contained way to *shape* a token so anyone holding the signing key can trust it.
- **OAuth** is a delegation **PROTOCOL** — a defined *flow* for letting one app access your data in another app **without ever seeing your password**.

A format vs a flow. Once that clicks, the "vs" mostly dissolves.

---

## JWT: a tamper-proof ID card you carry

A **JSON Web Token** is a string in three base64url-encoded parts joined by dots:

```text
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.K3v9-Hs...
└──── header ────┘ └──────── payload ────────┘ └─ signature ─┘
```

- **Header** — the algorithm (e.g. `HS256` or `RS256`).
- **Payload** — the *claims*: who the user is (`sub`), when it expires (`exp`), and whatever else you put there (`role`, `email`).
- **Signature** — the header + payload, signed with a secret (HMAC) or a private key (RSA/EC).

The magic is in that signature. When a request arrives carrying the token, the server doesn't look anything up — it just **re-computes the signature** and checks it matches. If it does, the claims are trustworthy; if a single byte of the payload was tampered with, the signature breaks. That's what "stateless" means: **the trust travels inside the token**, so no session table, no Redis, no DB round-trip per request.

```codetabs
### python | Python (PyJWT)
import jwt  # PyJWT

SECRET = "super-secret-key"

# issue a token at login
token = jwt.encode({"sub": "123", "role": "admin"}, SECRET, algorithm="HS256")

# verify it on every request — no DB lookup
claims = jwt.decode(token, SECRET, algorithms=["HS256"])  # raises if tampered/expired
print(claims["sub"], claims["role"])   # 123 admin
### typescript | TypeScript (jsonwebtoken)
import jwt from "jsonwebtoken";

const SECRET = "super-secret-key";

// issue a token at login
const token = jwt.sign({ sub: "123", role: "admin" }, SECRET, { algorithm: "HS256" });

// verify it on every request — no DB lookup
const claims = jwt.verify(token, SECRET) as { sub: string; role: string }; // throws if tampered/expired
console.log(claims.sub, claims.role); // 123 admin
### java | Java (jjwt)
import io.jsonwebtoken.Jwts;
import javax.crypto.SecretKey;

SecretKey key = io.jsonwebtoken.security.Keys.hmacShaKeyFor(SECRET.getBytes());

// issue a token at login
String token = Jwts.builder().subject("123").claim("role", "admin").signWith(key).compact();

// verify it on every request — no DB lookup
var claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload(); // throws if tampered/expired
System.out.println(claims.getSubject() + " " + claims.get("role")); // 123 admin
```

Two things people get wrong: a JWT is **signed, not encrypted** — anyone can base64-decode the payload and *read* it, so **never put secrets in it**. And because it's self-contained, you **can't easily un-issue one** before it expires — which is why JWTs are kept short-lived and paired with refresh tokens.

That's the whole of JWT. Notice what we *haven't* talked about yet: **how the user got the token in the first place.** That's a different question — and it's where OAuth lives.

---

## OAuth: a valet key for delegated access

**OAuth 2.0** answers a question JWT never touches: *how does one app get permission to act on your behalf in another app — without you handing over your password?*

The classic example is **"Log in with Google."** A new app wants your email and basic profile. The bad old way would be to type your Google password into that app (now it has *full* access to your account, forever). OAuth replaces that with a **valet key**:

1. The app redirects you to Google.
2. **Google** authenticates you (the app never sees your password) and asks: *"Allow this app to see your email and profile?"*
3. You approve. Google redirects back with a short **authorization code**.
4. The app exchanges that code (plus its own secret) for an **access token** — a key scoped to *only* email + profile, and revocable at any time.
5. The app calls Google's API with that token to fetch your data.

```text
You ──▶ App ──▶ Google (you log in + consent)
                  │
                  ▼
          authorization code ──▶ App exchanges for ──▶ access token (scoped, revocable)
                                                              │
                                                              ▼
                                                  App calls API on your behalf
```

The point of OAuth is **scoped, revocable delegation**: the third-party app gets a limited key, never your credentials, and you (or Google) can revoke it without changing your password. (When you also want to know *who* the user is, you add **OpenID Connect** on top of OAuth — and it returns an **ID token**, which is... a JWT.)

---

## So where's the "vs"?

There isn't one — they answer different questions:

<table>
  <thead><tr><th></th><th>JWT</th><th>OAuth 2.0</th></tr></thead>
  <tbody>
    <tr><td>What it is</td><td>A token format</td><td>A delegation protocol (a flow)</td></tr>
    <tr><td>Question it answers</td><td>"What shape is the token, and how do I trust it?"</td><td>"How does an app get a token at all, without my password?"</td></tr>
    <tr><td>Verifies by</td><td>Checking a signature (no lookup)</td><td>N/A — it's a flow, not a token</td></tr>
    <tr><td>Hands you</td><td>A self-contained signed string</td><td>An access token (often a JWT)</td></tr>
    <tr><td>Revocation</td><td>Hard (short TTL + refresh tokens)</td><td>First-class (revoke the grant)</td></tr>
    <tr><td>Use it when</td><td>You own client + server, need stateless login</td><td>A third party needs delegated access</td></tr>
  </tbody>
</table>

And the kicker that most "OAuth vs JWT" arguments miss: **they usually work together.** An OAuth flow ends by handing the app an access token — and that token is *very often a JWT*. It was never OAuth *or* JWT; it's **OAuth the protocol carrying JWT the format.**

---

## Which should you reach for?

- **Reach for a plain JWT** when you **own both the client and the server** and just need stateless login — your SPA or mobile app talks to your own API, you issue a short-lived JWT at login, verify it by signature on each request, and refresh it when it expires. No third party involved.
- **Reach for OAuth** when a **third party needs delegated access** — "Log in with Google/GitHub," letting one of your services call another on a user's behalf, or letting an external app touch your users' data. You want scoped, revocable grants and you never want passwords flowing through the wrong place.
- **Most real systems use both:** an OAuth/OpenID Connect flow to *get* a token, and JWTs as the *format* of the access/ID tokens that flow hands back, with short-lived JWTs + refresh tokens to balance statelessness against revocation.

So the next time someone asks "OAuth or JWT?", the senior answer is: *"Those aren't alternatives. JWT is the token format; OAuth is how you'd hand one out to a third party. What are you actually trying to do — log users into your own app, or let another app act on their behalf?"*

> Want the 90-second visual version? [Watch the reel.](https://youtube.com/shorts/6-k_Uqkx2U8)
