“Sign in with Google”, an app that reads your GitHub repositories, a backend service calling another API: these all run on OAuth 2.0, usually with OpenID Connect (OIDC) on top. OAuth is about authorization (granting limited access), and OIDC adds authentication (who the user is). Understanding the flows helps you integrate providers correctly, and run your own identity server if needed.

The roles

Role Example
Resource owner The user
Client Your app (web app, SPA, mobile app, backend)
Authorization server Google, GitHub, Microsoft Entra ID, Keycloak, Authentik
Resource server The API holding the data

The key idea: the user never gives your app their password. The authorization server authenticates the user and issues your app a token with limited permissions (scopes).

The authorization code flow (with PKCE)

This is the recommended flow for web apps, SPAs and mobile apps:

  1. Your app redirects the user to the authorization server:
https://auth.example.com/authorize?
  response_type=code
  &client_id=myapp
  &redirect_uri=https://app.example.com/callback
  &scope=openid profile email
  &state=Xyz123            # CSRF protection
  &code_challenge=…&code_challenge_method=S256   # PKCE
  1. The user logs in and consents.
  2. The server redirects back with a short-lived authorization code: https://app.example.com/callback?code=abc…&state=Xyz123
  3. Your backend (or the app) exchanges the code for tokens at the token endpoint, proving possession with the PKCE code_verifier (and the client secret, for confidential clients).
  4. You receive an access token (and, with OIDC, an ID token, and optionally a refresh token).

PKCE (Proof Key for Code Exchange) prevents stolen authorization codes from being used. It’s required for public clients (SPAs, mobile), and recommended for all clients in OAuth 2.1.

The implicit flow (tokens directly in the redirect URL) and the password grant (the app collects the user’s password) are deprecated. Don’t use them in new apps.

Client credentials flow (machine to machine)

For a backend service calling an API with no user involved:

curl -s -X POST https://auth.example.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=billing-service -d client_secret="$SECRET" \
  -d scope="invoices:read"

The response contains an access token for that service’s own permissions.

The tokens

Token Purpose Audience
Access token Call the API. Short-lived Resource server
ID token (OIDC) Tells the client who the user is (a JWT with sub, email…) Client only, not for calling APIs
Refresh token Get new access tokens without re-login. Long-lived, keep it secret Authorization server

Access tokens are often JWTs, but they can also be opaque strings, checked via an introspection endpoint. See JWT explained.

Scopes limit what the token can do: read:repos, calendar.readonly, openid email. Request the minimum scopes your app needs. Users see them on the consent screen, and smaller scopes limit damage if a token leaks.

OpenID Connect in one paragraph

OIDC adds an identity layer: the openid scope, the ID token, a userinfo endpoint, and standard discovery (/.well-known/openid-configuration) listing endpoints and signing keys. “Sign in with…” buttons and SSO for internal tools are OIDC.

Security essentials

  • Register exact redirect URIs, with no wildcards. Open redirectors and loose matching are classic token-theft bugs.
  • Validate state, or use PKCE plus nonce for OIDC, against CSRF and replay attacks.
  • Keep client secrets on the server. SPAs and mobile apps are public clients and can’t keep secrets, so they use PKCE without a secret.
  • Validate ID tokens fully: signature, iss, aud = your client ID, exp and nonce.
  • Store refresh tokens securely (server-side, or in HttpOnly cookies), and rotate them.
  • HTTPS everywhere for redirect URIs and endpoints.

Self-hosting an identity provider

For SSO across your own apps and internal tools, you can run an OIDC provider on a VPS:

Option Notes
Keycloak Full-featured, Java, heavier (2 GB+ RAM recommended)
Authentik Modern UI, flows, proxy outpost for apps without OIDC
Authelia Lightweight, forward-auth for Nginx and Traefik, plus OIDC provider
Zitadel Cloud-native, multi-tenant

Put internal tools behind SSO with MFA, rather than exposing separate logins. See zero trust networking.

Common mistakes

  • Using the ID token to call APIs, instead of the access token.
  • Treating OAuth access as authentication (“has a valid Google token” doesn’t mean “is this user of my app” without checking aud and sub).
  • Long-lived access tokens with broad scopes.
  • Logging tokens, or passing them in URLs.
  • Skipping state or PKCE.

Quick reference

Situation Flow
Web app with backend Authorization code + PKCE (confidential client)
SPA / mobile app Authorization code + PKCE (public client)
Service to service Client credentials
Devices without a browser (TV, CLI) Device authorization flow
User login / SSO OIDC (authorization code + openid scope)

Related: API security best practices and webhooks explained.

  • API
  • Security
  • Authentication