OAuth 2.0 and OpenID Connect explained, and how to implement them securely

"Sign in with Google" runs on OAuth and OpenID Connect. Learn the roles, the safe flow and the mistakes that cause account takeovers.

A login flow between an app, a user and an identity provider

OAuth 2.0 is a framework for delegated authorization: it lets a user give an application limited access to their resources on another service, without sharing their password. OpenID Connect (OIDC) is a thin layer on top of OAuth 2.0 that adds authentication: it tells your app who the user is. "Sign in with Google" is OIDC in practice.

This article explains the basics and the main secure-implementation rules. For real systems, prefer a well-maintained library or an identity provider over writing flows yourself.

The key difference

  • Authorization (OAuth 2.0): "This app may read my calendar." The result is an access token.
  • Authentication (OIDC): "This user is Alice." The result is an ID token (a signed token with identity claims such as subject, email and issuer).

Using a plain OAuth access token as proof of login is a classic mistake. Use OIDC and validate the ID token when you need to know who the user is.

The roles

  • Resource owner: the user.
  • Client: your application.
  • Authorization server (identity provider): authenticates the user and issues tokens (for example Google, Microsoft, Keycloak, Auth0).
  • Resource server: the API that accepts access tokens.

For web apps, mobile apps and single-page apps, the current best practice is the authorization code flow with PKCE (Proof Key for Code Exchange).

  1. Your app creates a random code verifier, derives a code challenge from it, and a random state value, and stores them.
  2. It redirects the user's browser to the authorization server with: client_id, redirect_uri, response_type=code, scope, state, and the code_challenge.
  3. The user logs in and approves the requested permissions at the authorization server (never in your app).
  4. The server redirects back to your redirect_uri with a short-lived authorization code and the state.
  5. Your app checks that state matches, then sends the code plus the code verifier (and client credentials if it is a confidential client) to the token endpoint from the back end.
  6. It receives an access token, often a refresh token, and for OIDC an ID token.
  7. Your app validates the ID token and creates its own session.

PKCE makes a stolen authorization code useless to an attacker who does not have the verifier. It is recommended for all clients, including server-side ones.

Flows to avoid

  • The implicit flow (tokens returned in the URL) is discouraged and removed in current guidance.
  • The resource owner password credentials flow (the app collects the user's password) defeats the purpose of OAuth. Avoid it.

Tokens and scopes

  • Scopes define what access is requested (for example openid email profile or calendar.read). Request the minimum.
  • Access token: short-lived. Send it to APIs in the Authorization: Bearer header. Treat it as a secret.
  • Refresh token: longer-lived, used to get new access tokens. Store it with great care, rotate it if supported and revoke on logout or compromise.
  • ID token (OIDC): a signed JWT for your client. Do not send it to APIs as an access token.

Validate everything

When you receive an ID token, check at least:

  • The signature, using the provider's published keys (JWKS), and the expected algorithm.
  • iss (issuer) matches your provider.
  • aud (audience) contains your client ID.
  • exp has not passed and nbf/iat are sane.
  • The nonce matches the one you sent (prevents replay).
  • Use the stable subject identifier (sub) plus issuer to identify the user, not the email alone, since emails can change or be unverified.

APIs receiving access tokens must validate the token (signature or introspection), the audience, the scope, and expiry.

Common implementation mistakes (and how they are exploited)

Missing or unchecked state. Without it, an attacker can trick a victim into completing a login that links to the attacker's account, or forge the callback (a CSRF on the login flow). Always generate a random state, bind it to the user's session and verify it.

Loose redirect_uri validation. If the authorization server accepts wildcard or partially matching redirect URIs, an attacker may steal the authorization code or token by redirecting it to their own site. Register exact redirect URIs and match them exactly. Do not allow open redirects on your own domain.

Not using PKCE for public clients, which allows code interception.

Trusting unverified email. Linking or logging in users by an email claim without checking email_verified and the issuer can allow account takeover. Be careful when merging accounts across providers.

Skipping ID token validation or accepting alg: none or the wrong algorithm.

Putting tokens in URLs or logs, or in localStorage where XSS can read them. Prefer HttpOnly, Secure, SameSite cookies for session handling, and a backend-for-frontend pattern for SPAs where practical.

Overly broad scopes and long-lived tokens.

Using the access token as an identity proof instead of OIDC.

Weak client secret handling. Never put a client secret in a mobile app or browser code. Those are public clients and must rely on PKCE.

Not handling logout and revocation, leaving sessions and refresh tokens alive.

Security checklist

  1. Use authorization code + PKCE, and a vetted library or identity provider.
  2. Register exact redirect URIs, use HTTPS.
  3. Generate and verify state and nonce.
  4. Validate ID tokens (signature, issuer, audience, expiry, nonce).
  5. Request minimum scopes, keep tokens short-lived, protect refresh tokens.
  6. Identify users by issuer + sub, and verify email before trusting it.
  7. Keep secrets server-side only, store tokens safely, never log them.
  8. Validate audience and scope in your APIs.
  9. Support revocation and logout, and monitor for anomalies.
  10. Keep libraries and provider configuration up to date, and follow current guidance such as the IETF OAuth security best current practice.

When to use what

  • Social or enterprise login: OIDC with a trusted provider.
  • Your API accessed by your own apps: OAuth with your own authorization server or a managed service.
  • Machine-to-machine: client credentials flow, with secrets stored securely.
  • Do not build your own authorization server unless you must. It is hard to get right.

Takeaway: OAuth 2.0 grants limited access and OIDC proves identity. Use the authorization code flow with PKCE, validate state, nonce and ID tokens, register exact redirect URIs, request minimal scopes and let a vetted library handle the details.


Web security series, Part 3 (accounts and defence): 2 of 5. Previous: Storing passwords safely: hashing, salting, bcrypt and Argon2.

Keep reading