Back to Articles
7 min read

Why We Use OAuth 2.0 and JWT (And How They Actually Work)

SecurityOAuth 2.0JWTAuthentication
Why We Use OAuth 2.0 and JWT (And How They Actually Work)

In modern software development, web applications rarely exist in isolation. They connect to APIs, integrate with third-party platforms, and delegate authentication to giant identity providers like Google, Apple, or GitHub.

Today, OAuth 2.0 and JSON Web Tokens (JWTs) have become the undisputed industry standards for authentication delegation and session management. According to identity reports from platforms like Okta and API security analyses from Salt Security, token-based authentication (specifically OAuth 2.0 and JWTs) now secures the vast majority of modern web traffic. But why did we end up with this specific stack? To build secure applications today, we must first understand the historical design choices, constraints, and failures that shaped these protocols.


The Origin: Why passwords alone failed us

In the early days of the web, if a third-party application wanted to access your data on another service (for instance, a photo-printing site wanting to access your photos on Yahoo), the solution was crude: you gave the third-party application your raw username and password.

This is known as the Password Anti-Pattern.

But why was this a disaster?

  1. No delegation control: The third party could do anything you could do. You couldn't restrict them to read-only access.
  2. No revocation without changing credentials: To revoke the third party's access, you had to change your password, which broke access for all other integrated apps.
  3. Storage risk: You had to trust that the third-party app stored your password securely. If they got hacked, your credentials for the primary service were exposed.

The industry needed a secure way to delegate authority without sharing credentials.


Enter OAuth: From Cryptographic Headache (1.0) to Pragmatic Simplification (2.0)

In 2007, an open group of developers created OAuth 1.0. It solved the password problem by introducing tokens. However, it was notoriously difficult to implement.

What was wrong with OAuth 1.0?

OAuth 1.0 required cryptographic signatures on every single HTTP request. Every client had to compute a complex signature base string, hash it using a shared secret, and send it in the headers. Getting the signature calculation right in different programming languages was a constant source of developer frustration and bugs.

In 2012, OAuth 2.0 (RFC 6749) was released. It made a fundamental design compromise:

  • Drop client-side cryptographic signing: Instead of signing every request, clients use simple "Bearer Tokens."
  • Delegate security to the transport layer: OAuth 2.0 mandates that all communication must occur over HTTPS (TLS/SSL). As long as the transport channel is encrypted, bearer tokens can be sent in plain text headers without signature computation.

Is OAuth 2.0 an "Authorization" or an "Authentication" framework?

This is the most common misunderstanding in modern web development. OAuth 2.0 does not tell a website who you are. It only issues an access token that represents permission to access specific resources, making it an authorization framework.

To use an analogy: an OAuth token is like a hotel keycard. The card doesn't have your name on it; it just opens the door to Room 302. The hotel check-in desk authenticated you (checked your passport), but the keycard itself only authorizes entry.

So how does OpenID Connect (OIDC) help us authenticate?

Because developers kept using OAuth 2.0 for authentication anyway. They would log a user in, get an access token, and guess that if the token was valid, the user was authenticated. This led to massive security gaps.

To fix this, OpenID Connect (OIDC) was built as an identity layer on top of OAuth 2.0. It standardized the process of authentication by introducing a new artifact: the ID Token. While the Access Token is meant for a machine to call an API, the ID Token is explicitly meant for the client application to know who logged in.


The Engine of OIDC: How JWTs Became the Standard

To represent this identity data in OIDC, the industry standardized on the JSON Web Token (JWT) (RFC 7519).

Historically, session management was stateful.

  • Stateful Sessions: The server generated a random session ID, sent it to the browser as a cookie, and stored the session details in a database (like Redis or PostgreSQL). Every time the user made a request, the server queried the database to find out who the user was.

But why did we switch to stateless JWTs?

As web applications shifted toward microservice architectures and global scale, stateful sessions hit a bottleneck:

  1. Database lookup latency: Scaling a central session database globally to handle millions of reads per second is difficult and expensive.
  2. Loss of session state: If the session database went down, everyone got logged out.
  3. Cross-domain issues: Traditional session cookies are difficult to share across different domains and microservices.

JWTs solved this by being stateless. A JWT is a self-contained container of data (claims). It has three parts separated by dots:

  1. Header: Dictates the algorithm used (e.g., {"alg": "RS256"}).
  2. Payload: Contains the claims (e.g., {"sub": "user_123", "email": "user@test.com", "exp": 1729864200}).
  3. Signature: A cryptographic proof generated by the identity server.

JWT Structure:

[Base64URL(Header)] . [Base64URL(Payload)] . [Signature]

When your backend receives a JWT, it does not query a database. It simply runs a cryptographic check on the signature using the identity provider's public key.

How does this mathematical check actually verify the token?

To verify a JWT, the server performs the following steps:

  1. Recompute the Hash: The server takes the first two parts of the token—the base64-encoded [Header] and [Payload]—concatenates them with a dot, and runs them through a hashing algorithm (like SHA-256).
  2. Decrypt the Signature: The server takes the [Signature] (the third part of the token) and decrypts it using the identity provider's public key.
  3. Compare the Values: The server compares the decrypted signature value against the newly computed hash of the header and payload.

If they match exactly, two things are mathematically guaranteed: the token was created by the holder of the private key (authentication), and not a single character in the header or payload has been altered since it was signed (integrity). This is a pure, CPU-bound mathematical operation, completely bypassing database lookups.


The Authorization Code Flow: A Step-by-Step Walkthrough

The most secure and widely used flow in OAuth 2.0 / OIDC is the Authorization Code Flow. Here is how it functions in a real-world scenario (like Logging in with Google):

Loading diagram...
  1. Authorization Request: The frontend redirects the user to Google's authorization page with query parameters indicating the requested permissions (scope=openid email) and where to return (redirect_uri).
  2. Consent & Auth: The user logs into Google and consents to share their email.
  3. The Auth Code: Google redirects the user back to the application's redirect_uri with a temporary, short-lived string called an Authorization Code in the URL.
  4. Token Exchange: The frontend sends this code to its backend server. The backend server then makes a direct server-to-server request to Google, sending the Authorization Code along with the application's private Client Secret.
  5. Token Issuance: Google validates the code and secret, and returns the tokens (including the ID Token as a JWT).

But why do we use an intermediary Authorization Code instead of just sending the JWT in the URL immediately?

If Google sent the JWT directly in the redirect URL, it would be exposed in the browser's history, address bar, and browser logs. By sending a temporary, single-use Authorization Code instead, the actual sensitive tokens are only ever transmitted via secure, direct server-to-server connections, protecting them from browser-side interception.


Summary

OAuth 2.0 and JWTs solved delegation and scale. By replacing raw password sharing with secure authorization flows, and replacing database-backed session tables with cryptographically signed tokens, they enabled the modern, interconnected cloud ecosystem we build on today. However, delegating security to cryptography means the implementation must be secure.

Read Next: Securing JWTs in Production

Written by Jaakko Hyöty

AI & Software Engineer. Moving AI systems into production.

← Back to Articles