OAuth 2.0 & OIDC
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
| Role | What it is | Example |
|---|---|---|
| Resource Owner | The user who owns the data and grants access | The person logging in |
| Client | The application requesting access on behalf of the user | A photo editing app wanting access to Google Photos |
| Authorization Server | Issues tokens after authenticating the user and getting consent | Google’s OAuth server, Auth0, Okta, Keycloak |
| Resource Server | Hosts the protected resources; validates tokens | Google Photos API |
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.
- 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 astateparameter (random value to prevent CSRF). - User authenticates: The authorization server shows a login page and consent screen. The user logs in and approves the requested scopes.
- Authorization code returned: The authorization server redirects back to
redirect_uriwith a short-lived authorization code and the originalstate. - Client verifies state: The client checks that the returned
statematches what it sent. A mismatch indicates a CSRF attempt. - Token exchange: The client’s backend sends the authorization code,
client_id,client_secret, andredirect_urito the token endpoint. The authorization server returns an access token (and optionally a refresh token). - 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:
- The client generates a random
code_verifierand derives acode_challenge = BASE64URL(SHA256(code_verifier)). - The
code_challengeis sent with the authorization request. - The
code_verifieris sent with the token exchange request. - The authorization server verifies that
SHA256(code_verifier) == code_challenge. An intercepted authorization code is useless without the originalcode_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:
- ID Token: A JWT returned alongside the access token that contains identity claims — the user’s ID, email, name, and profile information. The client uses the ID token to know who logged in.
- UserInfo endpoint: An API endpoint that returns the user’s profile information given a valid access token. For claims not included in the ID token.
- Standard scopes:
openid(required — triggers OIDC),profile(name, picture, locale),email(email address),address,phone. - Discovery document:
/.well-known/openid-configuration— a JSON endpoint that advertises the authorization server’s endpoints, supported flows, and public keys. Clients auto-configure from this URL.
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
- Always use PKCE. Even for confidential clients (those with a
client_secret), PKCE adds an extra layer against authorization code interception. It’s required for public clients and best practice for all clients. - Store tokens securely. Access tokens in
localStorageare vulnerable to XSS — any injected script can steal them. UseHttpOnlycookies for token storage in web apps (not accessible to JavaScript). For SPAs, consider the Backend For Frontend (BFF) pattern: the SPA calls a same-origin backend that holds the tokens and proxies API calls. - Validate all JWT claims. Always verify
sig(signature),exp(not expired),iss(expected issuer), andaud(your service is the intended audience). A JWT with a valid signature but wrong audience is still an attack. - Scope minimization. Request only the scopes your application needs. Broad scopes are a liability — a compromised token with
admin:*scope causes far more damage than one withread:profile. - Rotate refresh tokens. Issue a new refresh token every time one is used (refresh token rotation). Detect token reuse — if a refresh token is used twice, revoke the entire session (indicates the token was stolen). Major OAuth providers implement this by default.
- Use a proven authorization server. Implementing OAuth correctly is hard. Auth0, Okta, Keycloak, Cognito, and Dex are well-audited implementations. Rolling your own OAuth server is error-prone and rarely worth the effort for most applications.