OAuth 2.0 and OpenID Connect

Advanced
13 min

OAuth 2.0 and OpenID Connect

"Sign in with Google" and "Connect your GitHub account" both run on OAuth 2.0, and most login implementations add OpenID Connect (OIDC) on top. The protocols are well designed but have many optional parts, and the insecure combinations are easy to pick by accident.

In this lesson you will learn the roles and token types, the Authorization Code flow with PKCE, how to validate the callback and ID token, and the classic redirect and token-confusion mistakes.

Roles and Tokens

OAuth 2.0 is an authorization framework: a user (resource owner) lets your application (client) call an API (resource server) on their behalf, using an access token issued by an authorization server. OIDC adds authentication: the authorization server also returns an ID token, a JWT that states who the user is.

| Token | Audience | Purpose | Format | |---|---|---|---| | Access token | The API (resource server) | Authorise API calls | Opaque or JWT; the client does not inspect it | | ID token | Your application (client) | Prove who logged in | JWT; validate it, never send it to APIs | | Refresh token | The authorization server | Obtain new access tokens | Opaque; stored server-side only |

Confusing these is a common bug: an ID token is not an API credential, and an access token is not proof of identity.

The Authorization Code Flow with PKCE

The only flow you should use for user login is Authorization Code with PKCE (Proof Key for Code Exchange). The implicit flow, which returned tokens in the URL fragment, is deprecated because tokens leaked through browser history and referrers.

  1. The client generates a random code_verifier, hashes it into a code_challenge, creates a random state, and redirects the user to the authorization server (the sample code).
  2. The user authenticates there and consents.
  3. The server redirects back to the registered redirect_uri with a short-lived code and the same state.
  4. The client exchanges the code plus the code_verifier at the token endpoint; the server hashes the verifier and compares it with the challenge, so a stolen code is useless alone.
javascript
const res = await fetch("https://accounts.example.com/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, code_verifier: req.session.oauth.verifier, redirect_uri: "https://app.example.com/auth/callback", client_id: process.env.OAUTH_CLIENT_ID, client_secret: process.env.OAUTH_CLIENT_SECRET }) }); const { access_token, id_token } = await res.json();

Public clients (mobile and single-page apps) have no client_secret; PKCE protects them.

Validating the Callback and the ID Token

The callback handler is where most implementation bugs live. Before trusting anything:

javascript
if (req.query.state !== req.session.oauth.state) throw new Error("state mismatch"); // login CSRF
javascript
const { jwtVerify, createRemoteJWKSet } = require("jose"); const JWKS = createRemoteJWKSet(new URL("https://accounts.example.com/.well-known/jwks.json")); const { payload } = await jwtVerify(id_token, JWKS, { issuer: "https://accounts.example.com", audience: process.env.OAUTH_CLIENT_ID }); if (payload.nonce !== req.session.oauth.nonce) throw new Error("nonce mismatch"); // replayed token if (!payload.email_verified) throw new Error("unverified email"); req.session.regenerate(() => { req.session.userId = findOrCreateUser(payload.sub).id; });

Link accounts by the stable sub claim, never by email alone, and check email_verified: an attacker can register a provider account with an unverified email that matches a victim on your site.

Common Mistakes

  • Loose redirect URI matching. If the authorization server accepts https://app.example.com/anything, an open redirect on your site turns into code theft. Register exact URIs.
  • Skipping state. An attacker can complete a login with their own code in the victim's browser, logging the victim into the attacker's account (login CSRF).
  • Tokens in URLs or logs. Codes and tokens travel in POST bodies and headers, and are redacted from logs.
Quick Quiz
Question 1 of 2

What does PKCE protect against?

Key Takeaways

  • OAuth 2.0 delegates authorization with access tokens; OIDC adds authentication with ID tokens.
  • Use the Authorization Code flow with PKCE for every client type; the implicit flow is deprecated.
  • Verify state, then validate the ID token's signature, iss, aud, exp and nonce before creating a session.
  • Link users by sub and require email_verified; never treat an ID token as an API credential.
  • Register exact redirect URIs, request minimal scopes and keep tokens out of URLs and logs.

Next lesson: Software and Data Integrity Failures — trusting updates, pipelines and serialized data without verifying them.

OAuth 2.0 and OpenID Connect - Cyber Security | CodeYourCraft | CodeYourCraft