OAuth 2.0 and OpenID Connect Without Shortcuts

Security Intermediate 25 min Jan 12, 2026

Audience

This guide is for anyone who needs to understand OAuth 2.0 and OpenID Connect conceptually:

  • Backend developers implementing authentication and authorization in APIs
  • Frontend developers integrating “Login with Google/GitHub/etc.” buttons
  • Security engineers reviewing OAuth implementations for vulnerabilities
  • Technical architects choosing between authentication strategies
  • Product managers understanding SSO and third-party integrations

Basic understanding of HTTP and APIs is helpful but not required. We’ll build from first principles.

Goal

After reading this guide, you’ll understand:

  • What problem OAuth 2.0 solves (and what it doesn’t)
  • The four roles in every OAuth interaction
  • How the main OAuth flows work (Authorization Code, Client Credentials)
  • Why PKCE exists and what it protects against
  • The difference between OAuth 2.0 and OpenID Connect
  • When to use ID tokens vs. access tokens
  • How to choose the right flow for your application

You won’t be ready to implement OAuth securely (that requires deeper study), but you’ll have a clear mental model of how it all fits together.

1. The Problem OAuth Solves

Imagine you’re building a photo printing service. Users want to print photos from their Google Drive. The naive solution: ask users for their Google password.

This is terrible for multiple reasons:

  1. Trust: Users must trust you with their Google credentials
  2. Access scope: You get full access to their Google account, not just photos
  3. Revocation: If users want to stop using your service, they must change their Google password
  4. Security: You now store passwords for accounts you don’t own

OAuth solves this by enabling delegated authorization:

“Let users grant your application limited access to their resources on another service, without sharing their credentials.”

graph LR
    A[Photo Printing App] -->|"Can I access
Alice's photos?"| B[Google] B -->|"Alice, do you
approve this?"| C[Alice] C -->|"Yes, only
read photos"| B B -->|"Here's a token
for photos only"| A style A fill:#e3f2fd style B fill:#fff3e0 style C fill:#e8f5e9

Key insight: OAuth is about authorization (what you can do), not authentication (who you are).

What OAuth is NOT

OAuth does not tell you:

  • Who the user is
  • The user’s email or name
  • Whether the user is logged in to their account

OAuth only tells you: “This token grants access to these specific resources.”

This distinction matters. We’ll see how OpenID Connect adds identity later.

2. The Four Roles

Every OAuth interaction involves four distinct roles. Understanding them is essential.

graph TB
    subgraph "The Four OAuth Roles"
        RO[Resource Owner
The user who owns the data] C[Client
Your application] AS[Authorization Server
Issues tokens] RS[Resource Server
Hosts protected data] end RO -->|"Grants permission"| C C -->|"Requests token"| AS AS -->|"Issues token"| C C -->|"Presents token"| RS RS -->|"Returns data"| C style RO fill:#e8f5e9 style C fill:#e3f2fd style AS fill:#fff3e0 style RS fill:#fce4ec

Resource Owner

The Resource Owner is the user who owns the protected data. In our photo printing example, it’s the person whose Google Drive photos you want to access.

The resource owner can grant or deny access to their resources. They’re the ultimate authority.

Client

The Client is your application—the software that wants to access the resource owner’s data. It could be:

  • A web application
  • A mobile app
  • A desktop application
  • A backend service

Important: In OAuth terminology, “client” means your application, not the end user.

Authorization Server

The Authorization Server authenticates the resource owner and issues access tokens to the client. It’s the gatekeeper.

Examples:

  • Google’s OAuth server (accounts.google.com)
  • GitHub’s OAuth server (github.com/login/oauth)
  • Your company’s identity provider

The authorization server handles:

  • User authentication (login)
  • Consent screens (“Allow this app to…?”)
  • Token issuance
  • Token validation (sometimes)

Resource Server

The Resource Server hosts the protected resources and accepts access tokens. It’s where the data lives.

Examples:

  • Google Drive API
  • GitHub API
  • Your company’s internal API

In many systems, the authorization server and resource server are the same (or run by the same organization). But conceptually, they’re separate roles.

Role Summary

RoleQuestion It AnswersExample
Resource OwnerWhose data is it?Alice (the user)
ClientWhat app wants access?Photo Printing Service
Authorization ServerWho issues tokens?Google OAuth
Resource ServerWhere is the data?Google Drive API

3. Authorization Code Flow

The Authorization Code Flow is the most common and secure OAuth flow. It’s used when your application has a backend server that can securely store secrets.

When to Use It

  • Web applications with a server-side component
  • Mobile apps (with PKCE—more on this later)
  • Any scenario where the client can keep a secret

The Flow Step by Step

sequenceDiagram
    participant U as User (Browser)
    participant C as Client (Your App)
    participant AS as Authorization Server
    participant RS as Resource Server

    U->>C: 1. Click "Login with Google"
    C->>U: 2. Redirect to Google
    U->>AS: 3. User authenticates & consents
    AS->>U: 4. Redirect back with auth code
    U->>C: 5. Send auth code to backend
    C->>AS: 6. Exchange code for tokens
    AS->>C: 7. Return access token (& refresh token)
    C->>RS: 8. Request data with access token
    RS->>C: 9. Return protected data

Let’s walk through each step:

Step 1-2: Initiate the flow

User clicks “Login with Google” in your app. Your app redirects them to Google’s authorization endpoint:

https://accounts.google.com/o/oauth2/v2/auth?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=https://www.googleapis.com/auth/drive.readonly
  &state=xyz123

Key parameters:

  • response_type=code: We want an authorization code
  • client_id: Your app’s identifier (registered with Google)
  • redirect_uri: Where to send the user after authorization
  • scope: What permissions you’re requesting
  • state: Random value to prevent CSRF attacks

Step 3: User authenticates and consents

Google shows a login screen (if needed) and a consent screen:

“Photo Printing Service wants to:

  • View your Google Drive files

Allow / Deny”

Step 4-5: Authorization code returned

If the user approves, Google redirects back to your redirect_uri:

https://yourapp.com/callback?
  code=4/0AX4XfWh...
  &state=xyz123

The code is the authorization code—a short-lived, one-time-use token.

Step 6-7: Exchange code for tokens

Your backend server (not the browser!) exchanges the code for tokens:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=4/0AX4XfWh...
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Google responds with:

{
  "access_token": "ya29.a0AfH6SM...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "1//0g..."
}

Step 8-9: Access protected resources

Your app uses the access token to call Google’s API:

GET /drive/v3/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer ya29.a0AfH6SM...

Why the Extra Step?

Why not just return the access token directly? Why the code exchange?

Security. The authorization code passes through the user’s browser (in the URL). If we returned the access token there:

  • It would be visible in browser history
  • It could be logged by proxies
  • Malicious browser extensions could steal it

By requiring a backend exchange with the client_secret, we ensure:

  • Only your server can get the access token
  • The token never touches the browser
  • Even if someone steals the code, they can’t use it without your secret

4. Client Credentials Flow

The Client Credentials Flow is simpler—it’s for machine-to-machine communication where no user is involved.

When to Use It

  • Backend services calling other backend services
  • Scheduled jobs accessing APIs
  • Any scenario where there’s no human user

The Flow

sequenceDiagram
    participant C as Client (Backend Service)
    participant AS as Authorization Server
    participant RS as Resource Server

    C->>AS: 1. Request token with client credentials
    AS->>C: 2. Return access token
    C->>RS: 3. Request data with access token
    RS->>C: 4. Return data

Step 1-2: Request token

Your service authenticates directly with its credentials:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(client_id:client_secret)

grant_type=client_credentials
&scope=api.read api.write

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Step 3-4: Use token

Same as before—include the token in API requests.

No Resource Owner

Notice there’s no user in this flow. The client acts on its own behalf, not on behalf of a user. The access token represents the client’s permissions, not a user’s.

This is appropriate for:

  • Microservices communication
  • Batch processing jobs
  • Admin tools

5. Implicit Flow (Deprecated)

You might encounter the Implicit Flow in older documentation. It was designed for browser-based apps that couldn’t securely store secrets.

graph LR
    A[Browser App] -->|"Request"| B[Auth Server]
    B -->|"Token in URL fragment"| A
    style A fill:#ffcdd2
    style B fill:#ffcdd2

Why it’s deprecated:

  • Access token exposed in browser URL
  • Vulnerable to token leakage
  • No refresh tokens
  • PKCE provides a better solution

Don’t use Implicit Flow for new applications. Use Authorization Code Flow with PKCE instead.

6. PKCE Explained

PKCE (Proof Key for Code Exchange, pronounced “pixy”) is an extension that makes the Authorization Code Flow secure for public clients—applications that can’t keep a secret.

The Problem PKCE Solves

Mobile apps and single-page applications (SPAs) can’t securely store a client_secret. Anyone can:

  • Decompile a mobile app
  • View JavaScript source code

Without a secret, what stops an attacker from intercepting the authorization code and exchanging it for tokens?

The Solution: Code Verifier and Challenge

PKCE adds a dynamic secret for each authorization request:

sequenceDiagram
    participant C as Client
    participant AS as Auth Server

    Note over C: Generate random
code_verifier Note over C: Create code_challenge
= SHA256(code_verifier) C->>AS: 1. Auth request + code_challenge Note over AS: Store code_challenge AS->>C: 2. Return authorization code C->>AS: 3. Token request + code_verifier Note over AS: Verify:
SHA256(code_verifier)
== stored challenge? AS->>C: 4. Return tokens

How it works:

  1. Client generates a secret (code_verifier): A random string, e.g., dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

  2. Client creates a challenge: code_challenge = BASE64URL(SHA256(code_verifier))

  3. Authorization request includes the challenge:

https://auth.example.com/authorize?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  ...
  1. Token exchange includes the original verifier:
POST /token
grant_type=authorization_code
&code=AUTH_CODE
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  1. Server verifies: SHA256(code_verifier) must match the stored challenge

Why This Works

Even if an attacker intercepts the authorization code:

  • They don’t have the code_verifier
  • The challenge was hashed, so they can’t reverse it
  • They can’t complete the token exchange

PKCE should be used for all authorization code flows, not just public clients. It’s a defense-in-depth measure.

7. OpenID Connect: Identity Layer

OAuth 2.0 answers: “Can this app access these resources?”

But it doesn’t answer: “Who is the user?”

OpenID Connect (OIDC) is a layer on top of OAuth 2.0 that adds identity. It answers: “Who just logged in?”

graph TB
    subgraph "The Relationship"
        OAuth[OAuth 2.0
Authorization] OIDC[OpenID Connect
Identity + Authorization] end OAuth -->|"Built on top of"| OIDC style OAuth fill:#fff3e0 style OIDC fill:#e3f2fd

What OIDC Adds

  1. ID Token: A JWT containing user identity claims
  2. UserInfo Endpoint: An API to fetch user profile data
  3. Standard Scopes: openid, profile, email
  4. Standard Claims: sub, name, email, picture

When to Use OIDC

  • “Login with Google/GitHub/Facebook”
  • Single Sign-On (SSO)
  • Any time you need to know WHO the user is

When Plain OAuth is Enough

  • Accessing user resources (photos, files, repos)
  • Machine-to-machine communication
  • Any time you only need access, not identity

8. ID Token vs. Access Token

OIDC introduces a new token type: the ID Token. Understanding the difference between ID tokens and access tokens is crucial.

Access Token

Purpose: Grants access to resources

Audience: Resource servers (APIs)

Contents: Can be opaque (random string) or a JWT with authorization claims

Example use:

GET /api/photos
Authorization: Bearer ya29.a0AfH6SM...

Who validates it: The resource server

ID Token

Purpose: Proves user identity

Audience: Your application (the client)

Contents: Always a JWT with identity claims

Example decoded:

{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1704067200,
  "iat": 1704063600,
  "email": "[email protected]",
  "name": "Alice Smith",
  "picture": "https://lh3.googleusercontent.com/..."
}

Who validates it: Your application

Key Differences

AspectAccess TokenID Token
PurposeAccess resourcesIdentify user
AudienceResource serverClient application
FormatOpaque or JWTAlways JWT
Sent toAPIsNever sent to APIs
ValidityMinutes to hoursShort-lived

Common Mistake

Never send ID tokens to APIs as authorization.

ID tokens are for your application to know who logged in. Access tokens are for APIs to authorize requests.

# WRONG - ID token to API
GET /api/data
Authorization: Bearer eyJhbGciOiJSUzI1NiIs... (ID token)

# CORRECT - Access token to API
GET /api/data
Authorization: Bearer ya29.a0AfH6SM... (Access token)

9. Choosing the Right Flow

Here’s a decision tree for choosing the right OAuth flow:

flowchart TD
    A[Start] --> B{Is there a user?}
    B -->|No| C[Client Credentials Flow]
    B -->|Yes| D{Can your app
keep a secret?} D -->|Yes| E[Authorization Code Flow] D -->|No| F[Authorization Code Flow
+ PKCE] E --> G{Need identity?} F --> G G -->|Yes| H[Add OpenID Connect] G -->|No| I[Plain OAuth 2.0] style C fill:#fff3e0 style E fill:#e3f2fd style F fill:#e8f5e9 style H fill:#fce4ec

Summary Table

ScenarioFlowOIDC?
Web app with backendAuthorization CodeOptional
Single-page app (SPA)Authorization Code + PKCEUsually yes
Mobile appAuthorization Code + PKCEUsually yes
Backend serviceClient CredentialsNo
“Login with X” buttonAuthorization Code (+ PKCE)Yes

Modern Recommendations

  1. Always use PKCE for authorization code flows, even if you have a backend
  2. Never use Implicit Flow for new applications
  3. Use OIDC when you need to know who the user is
  4. Use plain OAuth when you only need to access resources

10. What’s Next

You now understand the conceptual foundation of OAuth 2.0 and OpenID Connect. You know:

  • Why OAuth exists (delegated authorization)
  • The four roles (resource owner, client, authorization server, resource server)
  • How the main flows work (authorization code, client credentials)
  • Why PKCE exists (security for public clients)
  • The difference between OAuth and OIDC (authorization vs. identity)
  • When to use ID tokens vs. access tokens

What This Guide Didn’t Cover

This guide intentionally avoided:

  • Implementation details: How to securely implement OAuth in your language/framework
  • Common vulnerabilities: CSRF, token leakage, redirect URI manipulation
  • Token storage: Where and how to securely store tokens
  • Refresh token rotation: Handling token expiration securely
  • Advanced flows: Device Authorization, Token Exchange

These topics require deeper study and hands-on practice.

Hands-on learning:

  • Try implementing OAuth with a provider (Google, GitHub, Auth0)
  • Use a well-tested library (never roll your own OAuth)
  • Study the RFC specifications (listed below)

Course Preview:

OAuth 2.0 Done Right (and Wrong)

A practical course covering:

  • Secure implementation patterns
  • Real-world attack scenarios
  • Token storage strategies
  • Common misconfigurations
  • Debugging OAuth flows

Coming soon to API/OS

Deepen your understanding by exploring these terms:

Standards & RFCs

  • RFC 6749 - OAuth 2.0 Authorization Framework
  • RFC 6750 - Bearer Token Usage
  • RFC 7636 - PKCE (Proof Key for Code Exchange)
  • OpenID Connect Core 1.0 - Identity layer specification

You’ve built a solid mental model of OAuth 2.0 and OpenID Connect. The next step is hands-on implementation—but now you’ll understand what you’re implementing and why.