Guide
OAuth 2.0 and OpenID Connect explained
Every "Sign in with Google" button, every third-party API integration, and most modern single sign-on (SSO) stack sits on OAuth 2.0 — a framework for delegated authorization — and often OpenID Connect (OIDC), which adds standardized authentication and identity claims. This guide explains what each piece does, walks through the authorization code flow with PKCE (the default for browser and mobile apps today), and lists the mistakes that turn a convenience feature into a security hole.
Authorization is not authentication
The most common confusion: OAuth 2.0 answers "may this app act on my behalf?" It does not, by itself, answer "who is this person?" When you approve a photo editor to access your cloud drive, OAuth issues an access token the editor sends to the drive API. The token proves the drive owner granted a specific scope of access; it is not a general-purpose login badge.
OpenID Connect is a thin identity layer on top of OAuth 2.0. An OIDC
provider (Google, Auth0, Okta, Azure AD, etc.) returns an ID token —
a signed JWT containing claims like sub (subject user id), email,
and iss (issuer). Your app verifies that JWT, then creates a local session.
OIDC uses OAuth's transport; the openid scope triggers identity artifacts
alongside (or instead of) API access tokens.
Wallet-based sign-in on blockchains is a different model: the user proves control of a keypair by signing a message, not by delegating API access through a central issuer. See connecting a Solana wallet to a dApp for that path. OAuth and wallets solve overlapping UX problems with different trust roots.
Roles in an OAuth deployment
| Role | Typical example | Responsibility |
|---|---|---|
| Resource owner | You, the human user | Grants or denies access to your data |
| Client | Web app, mobile app, CLI tool | Requests authorization; stores tokens securely server-side or in OS vaults |
| Authorization server | accounts.google.com, login.microsoftonline.com | Shows consent UI; issues authorization codes and tokens |
| Resource server | Gmail API, GitHub REST API | Validates access tokens; returns protected resources |
In pure OIDC login (no third-party API), the authorization server and the "resource" of identity claims are often the same provider. The ID token is the payload your app cares about.
The authorization code flow with PKCE
Early OAuth allowed public clients (SPAs, mobile apps without a confidential backend) to receive access tokens directly in the browser — a pattern now discouraged because any script on the page could steal them. Today's best practice for user-facing apps is the authorization code flow with PKCE (Proof Key for Code Exchange, RFC 7636).
-
Generate PKCE verifier + challenge. The client creates a random
code_verifierand sends a hashedcode_challengewhen starting the flow. -
Redirect to the authorization server. The user sees a consent screen
listing requested scopes (e.g.
openid profile email). - User approves. The browser returns to your registered redirect URI with a short-lived authorization code in the query string — not the access token.
-
Back-channel token exchange. Your server (or, for SPAs with caution,
the client) POSTs the code plus the original
code_verifierto the token endpoint. PKCE proves the same party that started the flow finishes it, blocking authorization code interception attacks. - Receive tokens. Response includes an access token (for APIs), often a refresh token (for long-lived sessions, if allowed), and for OIDC an ID token JWT.
Implicit flow and password grant are legacy. Do not build new products on them. Machine-to-machine integrations use the client credentials grant: no user, just a confidential client id + secret exchanging for an access token scoped to service APIs.
Scopes, tokens, and what to verify
Scopes
Scopes are strings the client requests and the user approves. They should follow the principle of least privilege: ask only for what you need right now. OIDC baseline scopes:
openid— required for OIDC; enables ID token issuanceprofile— name, picture, locale claimsemail— email andemail_verified
Provider-specific API scopes (e.g. https://www.googleapis.com/auth/drive.readonly)
gate access to resource servers. A login-only app should not request drive scopes "just in case."
Access tokens
Opaque or JWT-formatted bearer credentials sent in the Authorization header.
Resource servers validate issuer, signature (if JWT), expiry, audience, and that the token
includes the scope required for the endpoint. Treat access tokens like passwords in logs —
never embed them in URLs or client-side analytics.
ID tokens
OIDC ID tokens are JWTs meant for the client application, not for calling arbitrary third-party APIs. Verify:
- Signature against the provider's JWKS (
/.well-known/jwks.json) issmatches the expected issuer URLaudincludes your client idexpis in the future (with small clock skew tolerance)nonceif you sent one at authorization time (binds token to session)
Do not use ID token claims alone for high-assurance authorization on your backend without also checking server-side session state or fetching userinfo with a valid access token when fresh attributes matter.
Refresh tokens
Refresh tokens obtain new access tokens without re-prompting the user. Store them server-side in encrypted storage, rotate on use when the provider supports it, and revoke on logout. Public clients (mobile) should use OS secure enclaves; browser SPAs generally should not hold long-lived refresh tokens without a backend-for-frontend (BFF) pattern.
Redirect URIs, clients, and common attacks
Misconfigured OAuth causes more breaches than novel cryptography breaks. Harden these surfaces:
- Exact redirect URI allowlists. No open redirects, no wildcard subdomains unless you fully control DNS and understand the blast radius.
-
Separate clients per platform. Web, iOS, and Android should use distinct
client ids and redirect schemes (
https://vs custom URL schemes). - State parameter. Random per-request value checked on return prevents CSRF on the authorization callback.
- PKCE everywhere for public clients. Even "confidential" server apps benefit from PKCE as defense in depth.
- Never ship client secrets in frontends. SPAs and mobile binaries are not secret stores; use PKCE + backend token exchange or a BFF.
Phishing clones fake consent screens. Train users to check the browser address bar on the real issuer domain — the same habit that matters for wallet security and approving transactions only on sites you trust.
OIDC discovery and userinfo
OIDC providers publish metadata at
/.well-known/openid-configuration. That JSON document lists
authorization_endpoint, token_endpoint,
jwks_uri, supported scopes, and response types. Libraries (Auth.js, Passport,
Spring Security, etc.) consume discovery so you do not hard-code vendor URLs.
The UserInfo endpoint returns claims about the authenticated user when
called with a valid access token bearing the openid scope. ID tokens carry a
snapshot at login time; userinfo can reflect changes (email verified later, name updated).
For many apps, verifying the ID token once at callback plus storing sub in your
session database is enough.
Passwordless alternatives are gaining ground: WebAuthn passkeys bind credentials to origin and device, reducing shared-password risk. Read passkeys and WebAuthn explained for how FIDO2 differs from bearer-token delegation — the two can coexist (OIDC for federation, passkeys for phishing-resistant factors).
Implementation checklist
- Use authorization code + PKCE for browser and mobile login.
- Request minimal scopes; justify each scope in your privacy policy.
- Validate ID tokens server-side; do not trust decoded JWT payloads from the client.
- Store refresh tokens encrypted; support remote logout / token revocation.
- Register separate redirect URIs for staging and production; no catch-all paths.
- Log auth failures without logging raw tokens or authorization codes.
- Document session lifetime and what happens when the provider disables an account.