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. Objectives
  2. 1. The four roles and the flow you will use
  3. 2. The tokens that arrive and what they are for
  4. 3. Scopes and consent
  5. 4. An honest implementation in Django
  6. 5. When NOT to use OAuth
  7. 5. The complete map of OAuth2 flows (and the one you must never use)
  8. 6. TicketFlow's scopes: the design authorization (21) will thank you for

Stack: DRF + django-allauth/authlib · Project: TicketFlow Status: Published Prerequisite: Lesson 18 — Sessions vs tokens


Objectives

  1. Explain the Authorization Code + PKCE flow without drawing on the whiteboard.
  2. Distinguish the four roles (resource owner, client, auth server, resource server) and the tokens they issue.
  3. Add "sign in with Google" to TicketFlow using OIDC (id_token → local user) without inventing flows.

1. The four roles and the flow you will use

  • Resource owner: the user (owner of their identity/accounts).
  • Client: TicketFlow (your app requests access on their behalf).
  • Authorization server: Google/Auth0/Keycloak (authenticates and issues tokens).
  • Resource server: the API holding what is protected (Google's profile, or your API).

The modern flow (and the only one for SPA/mobile): Authorization Code + PKCE:

1. Frontend → Auth server:  /authorize?client_id&redirect_uri&code_challenge&state
2. User signs in at the AUTH SERVER (you never see their password)
3. Auth server → redirect_uri:  ?code=ABC&state=...
4. Frontend → YOUR backend:  code + code_verifier
5. Your backend ↔ Auth server:  exchanges the code for tokens (with client_secret + verifier)
6. Your backend validates the id_token (JWKS signature, iss, aud, exp) → local user (create/match)

PKCE (code_challenge = hash of the verifier) prevents an intercepted code from being redeemable (redemption requires the verifier known only to the client that started it). state links the response to the request (the OAuth flow's CSRF).

2. The tokens that arrive and what they are for

  • id_token (OIDC): a JWT carrying identity (sub, email, email_verified): it is what you VALIDATE to create your user. Don't use it to call APIs.
  • access_token: for calling the provider's resource server (Google's API on the user's behalf). TicketFlow rarely needs it (you don't read their calendar): if you only do login, validate the id_token and discard the rest.
  • The provider's refresh_token: only if you need to call its API continuously.

Interview mistakes: "I use Google's access token to authenticate against MY API" — no: your API validates YOUR session/JWT (18) issued after validating the id_token. Social login is an authentication mechanism, and your system issues its own identity.

You ask for the minimum: openid email profile for login. Each extra scope (calendar, contacts) asks for trust and provider review. In your own API (if TicketFlow offered an API to third parties), the scopes would be YOUR contract: tickets:read, tickets:write — with the local access token carrying scope and your authorization (21) checking them.

4. An honest implementation in Django

django-allauth (or authlib) for the flow: Google/OIDC provider, callback that validates the id_token (the auth server's JWKS, aud = your client_id, correct iss, unexpired exp) and does account linking (same verified email → same local account; unverified email → never link, create a separate account or reject). With the local user, you issue YOUR auth (18): session or JWT. The identity provider can go down: your auth goes on (the user already exists locally; social login is only the entrance door).

5. When NOT to use OAuth

OAuth solves "signing in with someone else's identity". For an internal backend with 5 users, your own login + MFA (20) is simpler. And never implement the flow by hand with requests: libraries handle PKCE, JWKS caching, nonce and states — reinventing it manufactures vulnerabilities.

5. The complete map of OAuth2 flows (and the one you must never use)

OAuth2 defines more flows than you have heard of; the honest map:

FlowWhoStatusUse
Authorization Code + PKCEApp with backend or SPAthe current standardTicketFlow web, mobiles, SPAs
Client Credentialsservice→service, no uservalidLesson 29's worker asking the gateway for tokens
Device CodeTV, consoles, IoTvalid, nicheThe physical box-office terminal
Implicitold SPADEPRECATEDNone (the tokens traveled in the URL fragment: they leaked)
Resource Owner Passwordapps trusted by the providerDEPRECATEDNone (the user hands over their password: it breaks MFA, 20)

PKCE (Proof Key for Code Exchange) in three steps: the client generates a random code_verifier (43-128 chars), sends its hash code_challenge = S256(verifier) when starting, and when redeeming the code proves the verifier. It ensures an intercepted code (a malicious app in the redirect, a proxy log) is useless without the verifier — mandatory in public app flows SINCE OAuth 2.1.

6. TicketFlow's scopes: the design authorization (21) will thank you for

A scope is the permission the USER grants the APP (do not confuse it with lesson 21's roles, which are the user's permissions in YOUR system). TicketFlow's design:

openid profile email        ← identity (OIDC: the id_token and its claims)
tickets:read                ← read availability and events (public anyway)
tickets:purchase            ← reserve and pay (the scope the checkout asks for)
organizer:manage_events     ← create/edit events (organizer apps only)
organizer:read_sales        ← sales reports (lesson 31's CSV)
offline_access              ← refresh token (18): ask for it EXPLICITLY

The rules: scopes in plural and per resource (tickets:purchase, not write); the access token carries its scopes and EVERY endpoint verifies its own (lesson 21's 403 when the scope doesn't cover it); the front asks for the minimum (lesson 23's minimization principle); and the IdP's consent screen shows in human language what each scope grants — the user decides informed, not with jargon.


  1. What exact problem does PKCE solve, and which attack does it cancel in SPA/mobile?
  2. Why do you validate the id_token instead of using Google's access_token to authenticate against your API?
  3. An attacker registers the unverified "juan+promo@gmail.com" and your system links by email: what do they get? Which rule prevents it?
  4. What does your backend check in the id_token besides the signature (at least 4 claims)?
  5. Why doesn't your social login depend on the provider's uptime after the first login?

Continue with the exercises. The solutions only after trying it yourself.