Skip to main content

Command Palette

Search for a command to run...

Understanding OAuth 2.0 and OpenID Connect by Building a Minimal System

Updated
4 min read
Understanding OAuth 2.0 and OpenID Connect by Building a Minimal System

Over the past few days, I set out to understand OAuth 2.0 and OpenID Connect (OIDC) beyond surface-level usage. Instead of relying on libraries, I tried to build a minimal version of the flow myself.

This approach was slower than expected, and at times frustrating, but it helped me connect the pieces into a coherent system rather than a collection of endpoints.

This article summarizes that understanding.

Rethinking OAuth: It’s Not Just “Login”

A common simplification is:

“OAuth is used for login (e.g., Login with Google)”

While that’s a popular use case, it’s not the core idea.

OAuth is fundamentally about:

granting controlled access to resources on behalf of a user

OpenID Connect builds on top of OAuth to provide:

identity information about the user

Key Actors in the System

Any OAuth/OIDC flow involves three primary components:

Resource Owner (User) — the person whose data is being accessed Client — the application requesting access Authorization Server — the system that authenticates users and issues tokens

In many implementations, the authorization server and resource server may be separate, but in a minimal system, they can be combined.

Step 1: Client Registration

Before participating in the flow, a client must be registered with the authorization server.

The server issues:

client_id — public identifier client_secret — confidential credential

These are used later to authenticate the client during token exchange.

Step 2: User Authentication

The user signs up or logs in through the authorization server.

At this stage:

The system knows who the user is No permissions have yet been granted to any client

Authentication and authorization are intentionally separate concerns.

Step 3: Authorization Request

When the user initiates “Login with X”, the client redirects the user’s browser to the authorization server:

/authorize?client_id=...&redirect_uri=...&scope=...&state=...

The authorization server performs:

Client validation Session check (is the user logged in?)

If the user is not authenticated, they are redirected to the login flow first.

Step 4: User Consent

Once authenticated, the user is presented with a consent screen indicating what the client is requesting (defined by scope).

The user can:

Approve the request Deny the request

This step is central to OAuth’s permission model.

Step 5: Authorization Code Issuance

If the user approves, the authorization server:

Generates a short-lived authorization code Stores it with: userId clientId scope redirectUri expiration time Redirects the browser back to the client: redirect_uri?code=...&state=...

Important:

The authorization code is not an access token. It is an intermediate credential.

Step 6: Token Exchange (Server-to-Server)

This is a critical distinction in the flow.

After receiving the code, the client’s backend (not the browser) sends a request to the authorization server:

POST /token

With:

client_id client_secret code redirect_uri

This is a secure, server-to-server interaction.

Step 7: Validation and Code Exchange

The authorization server validates:

Client credentials (client_id and client_secret)

Authorization code existence and integrity

Matching redirect_uri

Code expiration

Client association (code must belong to that client)

If valid:

The authorization code is invalidated (one-time use)

Step 8: Token Issuance

The server responds with:

access_token

id_token (in OpenID Connect)

metadata such as expires_in and token_type

The access token is used to access protected resources.

The id_token contains identity claims about the user (e.g., subject, email).

Step 9: Accessing Protected Resources

The client backend uses the access token to call protected endpoints:

GET /userinfo Authorization: Bearer <access_token>

The resource server:

verifies the token returns user information

The client can then establish its own session for the user.

A Mental Model That Helped :-

One distinction simplified the entire flow:

/authorize → involves the user and browser /token → involves server-to-server communication

Understanding this separation clarified why the authorization code exists and why tokens are not returned directly in the browser.

Building a minimal version of OAuth 2.0 and OpenID Connect helped me realize that the real complexity doesn’t lie in individual endpoints, but in how they work together as a system.

There were moments where progress felt slow, mostly because my understanding of the overall flow wasn’t clear yet. Once that mental model started to form, the implementation became much more straightforward.

This is still a simplified interpretation, and there’s more to explore—especially around security and production-grade considerations—but working through the fundamentals has made the concepts far more intuitive.

Approaching it this way may take more time than using existing libraries, but it builds a level of clarity that is difficult to achieve otherwise.