Production Concerns

OAuth 2.0 & OIDC

● Intermediate ⏱ 12 min read production

OAuth 2.0 is an authorization framework that lets users grant third-party applications access to their resources without sharing their credentials. OpenID Connect (OIDC) is an identity layer built on top of OAuth 2.0 that adds authentication — who the user is, not just what they can access. Together they form the foundation of modern web authentication and API authorization.

The Problem OAuth Solves

Before OAuth, the common pattern for third-party access was credential sharing: “Give us your Gmail password and we’ll import your contacts.” This is dangerous — the third party has full account access, the user can’t revoke it without changing their password, and every service that stores your password is a breach vector.

OAuth solves this with delegated authorization: the user grants a specific application specific permissions (scopes) for a specific duration. The application never sees the user’s password. Permissions can be revoked independently for each application. The resource server can verify tokens without calling back to the authorization server on every request.

Roles and Terminology

RoleWhat it isExample
Resource OwnerThe user who owns the data and grants accessThe person logging in
ClientThe application requesting access on behalf of the userA photo editing app wanting access to Google Photos
Authorization ServerIssues tokens after authenticating the user and getting consentGoogle’s OAuth server, Auth0, Okta, Keycloak
Resource ServerHosts the protected resources; validates tokensGoogle Photos API
OAuth 2.0 authorization code flow: user, client, authorization server, and resource server interactions

Authorization Code Flow

The authorization code flow is the most secure and most common flow for web applications with a backend server. It never exposes tokens to the browser.

  1. Client initiates: The user clicks “Sign in with Google.” The client redirects the browser to the authorization server with: client_id, redirect_uri, response_type=code, scope, and a state parameter (random value to prevent CSRF).
  2. User authenticates: The authorization server shows a login page and consent screen. The user logs in and approves the requested scopes.
  3. Authorization code returned: The authorization server redirects back to redirect_uri with a short-lived authorization code and the original state.
  4. Client verifies state: The client checks that the returned state matches what it sent. A mismatch indicates a CSRF attempt.
  5. Token exchange: The client’s backend sends the authorization code, client_id, client_secret, and redirect_uri to the token endpoint. The authorization server returns an access token (and optionally a refresh token).
  6. Resource access: The client uses the access token as a Bearer token in API calls to the resource server: Authorization: Bearer <access_token>.

Why two steps (code then token)? The authorization code travels through the browser (URL redirect), where it could be exposed in browser history or referrer headers. The actual token exchange happens server-to-server and never touches the browser. Even if the code is intercepted, the attacker also needs the client_secret to exchange it.

PKCE (Proof Key for Code Exchange)

For public clients that can’t keep a client_secret (single-page apps, mobile apps), PKCE replaces the secret with a cryptographic challenge:

  1. The client generates a random code_verifier and derives a code_challenge = BASE64URL(SHA256(code_verifier)).
  2. The code_challenge is sent with the authorization request.
  3. The code_verifier is sent with the token exchange request.
  4. The authorization server verifies that SHA256(code_verifier) == code_challenge. An intercepted authorization code is useless without the original code_verifier.

PKCE is now recommended for all client types, not just public clients.

Other Flows

Client Credentials Flow: Machine-to-machine authentication with no user involvement. A backend service authenticates directly to the authorization server using its client_id and client_secret and receives an access token. Used for service accounts, scheduled jobs, and API-to-API calls.

Device Authorization Flow: For devices with limited input capability — smart TVs, CLI tools, IoT devices. The device displays a URL and a code. The user visits the URL on a separate device (phone, laptop), logs in, and enters the code. The device polls the token endpoint until the user completes authorization.

Implicit Flow (deprecated): Tokens returned directly in the redirect URL, no code exchange. Convenient but insecure — tokens are exposed in browser history, server logs, and referrer headers. Replaced by Authorization Code + PKCE for all public clients.

Tokens

Access Token: A credential that grants access to protected resources. Typically a JWT (JSON Web Token) containing claims about the user, the client, and the granted scopes. Short-lived — 15 minutes to 1 hour is common. The resource server validates the token on every API call, usually by verifying the JWT signature without calling back to the authorization server.

Refresh Token: A long-lived credential (days to months) used to obtain new access tokens without re-authenticating the user. Stored securely on the server — never in the browser. When the access token expires, the client sends the refresh token to the token endpoint to get a new access token.

JWT structure: Three Base64URL-encoded parts separated by dots: header.payload.signature. The payload contains claims:

{
  "sub": "user-123",         // subject (user ID)
  "iss": "https://auth.example.com",  // issuer
  "aud": "api.example.com",  // audience (intended recipient)
  "exp": 1700000000,         // expiry (Unix timestamp)
  "scope": "read:orders write:orders"
}

The resource server verifies the signature using the authorization server’s public key (fetched from the JWKS endpoint) and checks exp, iss, and aud. No database call required — validation is purely cryptographic.

OpenID Connect

OAuth 2.0 is an authorization framework — it grants access to resources, but it doesn’t say who the user is. “This token can read the user’s photos” is authorization. “This token belongs to alice@example.com” is authentication. OpenID Connect adds the authentication layer.

OIDC extends OAuth 2.0 with:

💡
OAuth is Not Authentication

A common mistake: using the presence of a valid OAuth access token to determine if a user is “logged in.” OAuth access tokens prove a user granted access, not who the user is. Use OIDC ID tokens for authentication. The ID token’s sub claim is the stable, unique user identifier — not the email address (which can change).

Design Considerations