“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:
- 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
- The user logs in and consents.
- The server redirects back with a short-lived authorization code:
https://app.example.com/callback?code=abc…&state=Xyz123 - 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). - 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 and consent
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 plusnoncefor 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,expandnonce. - 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
audandsub). - Long-lived access tokens with broad scopes.
- Logging tokens, or passing them in URLs.
- Skipping
stateor 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.