Module 4 · Authentication and security

Lesson 19 — OAuth2 and OpenID Connect

Flows, scopes and why "sign in with Google" is harder than it looks.

Published
In this lesson
  1. Exercise 1 — The flow
  2. Exercise 2 — The id_token
  3. Exercise 3 — Linking
  4. Exercise 4 — Scopes
  5. Exercise 5 — ADR-0009 (skeleton)
  6. Professor's summary

Exercise 1 — The flow

1-2. In your traffic: /authorize with response_type=code, code_challenge (S256), a random state; redirect with ?code=...&state=...; redemption (POST with code_verifier). Everything from the lesson, but yours.

  1. Altered state → allauth error (denied): it prevents flow CSRF (the callback of a login started by ANOTHER user being injected into you).

Exercise 2 — The id_token

  1. Standard OIDC claims: iss (https://accounts.google.com), aud (your client_id), sub (stable user id), email, email_verified, exp, iat, nonce (if you asked for it), name/picture.
  2. The JWT header carries kid; Google's JWKS has several rotated public keys; you select by kid and verify RS256. Caching JWKS (with a refresh on unknown kid) is mandatory: don't query JWKS on every login.
  3. Minimum checks: correct iss (prevents tokens from another tenant/provider), aud = your client_id (prevents reuse of tokens issued to another app), unexpired exp/iat (prevents replay of old tokens), nonce if you sent one (prevents replay of an injected code/token), and email_verified for linking (prevents account takeover).

Exercise 3 — Linking

  1. A (both verified): link — same human, one account. You add the social account to the local user; you don't create a duplicate (duplicating breaks their purchase history).
  2. B: NEVER link without email_verified. Create a separate account or reject with a verification message. The attack without this rule: register at a lax provider with the victim's email → you take over their account in your system.
  3. C: two local accounts (distinct identities until ownership is proven). Unification: an authenticated manual flow ("these accounts are yours" with proof via both providers) or support — automating the merge of purchases is an integrity problem (payments in whose name) you don't improvise.

Exercise 4 — Scopes

  1. has_permission reads request.auth.scope (if you issued your tokens with scope) or maps the client (registered application → granted scopes): events:read for listings, tickets:write to reserve, refunds:write only for backoffice/trusted integrators.
  2. 403 with {"type": ".../missing-scope", "title": "Missing the tickets:write scope", "status": 403}: the integrator knows what to ask for without support.

Exercise 5 — ADR-0009 (skeleton)

Context: password registration causes abandonment and manual email verification. Decision: OIDC (Google) with allauth, linking by verified email, local account always. Rejected alternatives: password only (friction), magic links (they depend on email anyway, less standard). Consequences: cached JWKS, mandatory nonce/state, a local account decouples the provider's uptime after the first login; the password remains as an alternative method + MFA (20).


Professor's summary

  • Authorization Code + PKCE is the only flow you will implement in 2026; the roles and state/nonce are part of the design, not the form.
  • id_token = identity (validate it and create your user); access_token = calling the provider's APIs. Your API consumes YOUR identity issued after validating.
  • Linking only with a verified email; otherwise you manufacture account takeover.
  • Social login is the door; your system issues and governs the session. The provider can go down afterwards.

Next: Lesson 20 — Password hashing and MFA.