OAuth 2.0 & OpenID Connect Explained
The authorization code flow with PKCE, scopes, ID tokens vs access tokens, and how "Sign in with Google" really works.
The problem OAuth solves
Imagine a study app that wants to read your Google Calendar to plan revision sessions. Before OAuth, it would ask for your Google password — giving it total access to your account. OAuth 2.0 lets you grant the app limited, revocable access (just "read calendar") without sharing your password.
OAuth 2.0 vs OpenID Connect
- OAuth 2.0 is about authorization — giving an app permission to access an API on your behalf. The result is an access token.
- OpenID Connect (OIDC) is a layer on top of OAuth 2.0 for authentication — proving who the user is. It adds an ID token (a JWT) containing the user's identity.
"Sign in with Google/Microsoft/GitHub" is OpenID Connect.
The four roles
| Role | Example |
|---|---|
| Resource owner | You, the student |
| Client | The study app |
| Authorization server | Google's login and consent service |
| Resource server | The Google Calendar API |
The authorization code flow with PKCE
This is the recommended flow for web apps, mobile apps and single-page apps:
1. App → Browser: redirect to the authorization server with
client_id, redirect_uri, scope, state, code_challenge
2. User logs in at the provider and approves the requested scopes
3. Provider → App: redirect back to redirect_uri?code=AUTH_CODE&state=...
4. App backend → Provider: exchange code + code_verifier for tokens
5. Provider → App: access_token (+ refresh_token, + id_token for OIDC)
6. App → API: Authorization: Bearer <access_token>
The short-lived authorization code travels through the browser; the tokens are exchanged in a back-channel request.
PKCE: protecting the code
PKCE (Proof Key for Code Exchange) stops attackers who intercept the authorization code from using it:
- The app creates a random code verifier.
- It sends a code challenge = Base64URL(SHA-256(verifier)) in step 1.
- In step 4 it sends the original verifier; the server hashes it and checks it matches.
An attacker with only the code can't produce the verifier.
import base64
import hashlib
import secrets
def make_pkce_pair() -> tuple[str, str]:
verifier = secrets.token_urlsafe(64) # 86 characters (allowed range: 43–128)
digest = hashlib.sha256(verifier.encode("ascii")).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
return verifier, challenge
verifier, challenge = make_pkce_pair()
print(len(verifier), challenge)
Use PKCE for every client today — including server-side web apps.
The state parameter
Generate a random state, store it in the user's session, and check it when the provider redirects back. A mismatch means the response wasn't started by this user — it blocks CSRF on the login flow.
Scopes and consent
Scopes limit what an access token can do: openid email profile for sign-in, calendar.readonly for reading a calendar. Request the minimum scopes you need — users are more likely to approve, and a leaked token can do less damage.
ID token vs access token
| ID token (OIDC) | Access token (OAuth) | |
|---|---|---|
| Purpose | Tells your app who logged in | Lets the app call an API |
| Audience | Your app's client ID | The API (resource server) |
| Format | Always a JWT | JWT or an opaque string |
| Should you send it to APIs? | No | Yes |
When handling an ID token, verify its signature (using the provider's published keys), issuer, audience, expiry and the nonce you sent.
Other grant types
- Client credentials — machine-to-machine access with no user (a backend job calling another API).
- Device authorization — for TVs and CLIs that can't easily show a browser.
- Implicit and password grants — deprecated; don't use them in new apps.
Practical advice
- Use a well-maintained OIDC client library or your framework's integration rather than hand-rolling the flow.
- Register exact redirect URIs; never allow open redirects.
- Store refresh tokens securely and rotate them.
- Treat third-party profile data (like email) as verified only if the provider marks it verified.
Common mistakes
- Using OAuth access tokens to identify users. Use the OIDC ID token or the userinfo endpoint.
- Skipping
stateor PKCE. - Wildcard redirect URIs, enabling token theft through open redirects.
- Requesting broad scopes "just in case".
Key takeaways
- OAuth 2.0 = delegated authorization; OpenID Connect = authentication on top.
- Use the authorization code flow with PKCE and a random state.
- ID tokens identify the user to your app; access tokens call APIs.
- Request minimal scopes and verify every token you accept.