When you log into a third-party app “with Google” — that’s OAuth 2.0. When a backend service authenticates to AWS using a client ID and secret — also OAuth 2.0. The protocol is everywhere, but most developers’ understanding stops at “there’s a redirect and you get a token.” This post is the practical primer: what OAuth actually does, the flows that matter in 2026, when to use each, and the security details production systems get wrong.
What OAuth Is (And Isn’t)
OAuth 2.0 is an authorization framework. It lets an application get a token to access resources on behalf of a user (or a service), without that application ever seeing the user’s password.
OAuth is not authentication. It’s authorization — “this app may access X on behalf of this user.” For authentication, OpenID Connect (OIDC) extends OAuth to include identity verification. In practice, “log in with Google” uses OIDC on top of OAuth.
The roles in OAuth:
- Resource owner — Usually the user.
- Client — The application requesting access.
- Authorization server — Issues tokens (e.g., Google’s OAuth server).
- Resource server — Hosts the protected resource (e.g., Google APIs).
The dance: the user authenticates with the auth server, grants the client some scopes, and the client gets a token to use against the resource server.
The Flows
OAuth 2.0 defined several flows. Some are deprecated; others are the standard.
Authorization Code with PKCE (the modern default)
Used by web apps, mobile apps, SPAs. The most secure flow.
- Client redirects user to auth server with a random
code_challenge(derived from acode_verifier). - User authenticates and grants scopes.
- Auth server redirects back to client with an authorization
code. - Client exchanges
code+code_verifierfor an access token. - Client uses the access token to call resource APIs.
PKCE (Proof Key for Code Exchange) prevents an attacker who intercepts the code from exchanging it for a token without knowing the original code_verifier. Modern: always use PKCE, even for confidential clients.
Client Credentials
Used by service-to-service authentication. No user involved.
- Client sends its
client_idandclient_secretdirectly to the auth server. - Auth server returns an access token.
- Client uses the access token.
Simple, server-side, no redirects. Used for things like “my backend authenticates to AWS” or “my microservice authenticates to another microservice.”
Refresh Token
Not a flow in itself but a complement. After getting an access token, the client also gets a refresh token. When the access token expires, the client exchanges the refresh token for a new access token without involving the user.
Implicit (deprecated)
SPAs used to use this. Now superseded by Authorization Code + PKCE. Don’t use in new code.
Resource Owner Password Credentials (deprecated)
Client takes the user’s password directly. Don’t use — defeats the point of OAuth.
Device Code
For input-limited devices (TVs, IoT). The device shows a code; user authenticates on a different device.
A Concrete Example: “Log in with Google” via OIDC
For a typical web app:
1. User clicks "Log in with Google"
2. Browser redirected to:
https://accounts.google.com/o/oauth2/v2/auth
?client_id=<your_client_id>
&redirect_uri=<your_callback>
&response_type=code
&scope=openid email profile
&state=<random>
&code_challenge=<base64url(sha256(verifier))>
&code_challenge_method=S256
3. User logs in, grants permissions
4. Browser redirected to your callback:
https://yourapp.com/callback?code=<auth_code>&state=<random>
5. Your backend exchanges:
POST https://oauth2.googleapis.com/token
grant_type=authorization_code
code=<auth_code>
redirect_uri=<your_callback>
client_id=<your_client_id>
client_secret=<your_client_secret>
code_verifier=<the original verifier>
6. Receives:
{
"access_token": "<...>",
"refresh_token": "<...>",
"id_token": "<JWT with user identity>",
"expires_in": 3600
}
7. Your backend validates the ID token (a JWT) for user identity.
Optionally uses access_token to call Google APIs on user's behalf.
The id_token is the OIDC part — a JWT containing user identity claims. Verify its signature with Google’s public keys (from JWKS), check iss, aud, exp. Done.
Scopes
Scopes are strings naming what the client can access. The user sees and approves these:
openid— Triggers OIDC identity flow.email— User’s email address.profile— User’s name and basic profile.https://www.googleapis.com/auth/drive.readonly— Read Google Drive.
Scopes are application-defined; standard ones exist for common APIs. Granular scopes are good practice — request only what you need.
State Parameter
The state parameter prevents CSRF on the OAuth callback. Generate a random value, store it in the user’s session, and verify it matches when the callback fires.
Without state, an attacker can trick a victim’s browser into completing an OAuth flow that authorizes the attacker’s account on the victim’s session. Easy to miss; sneaky to exploit.
Always use state. Always verify it on the callback.
PKCE in Detail
PKCE solves the “intercepted code” attack:
- Client generates a random
code_verifier(43-128 char string). - Computes
code_challenge = base64url(sha256(code_verifier)). - Sends
code_challengein the authorization request. - Receives the
codeback. - Exchanges
code+ the originalcode_verifier(not the challenge) for the token.
If an attacker intercepts the code, they need the code_verifier to use it. Since the client kept the verifier secret, they can’t.
For SPAs and mobile apps where the client secret isn’t really secret, PKCE is essential. For server-side confidential clients, PKCE is best practice (and required by some providers).
Token Storage
Where you store tokens depends on the client type:
- Web app (SPA): httpOnly + SameSite cookies for tokens; the SPA itself never sees the token directly. Backend acts as a proxy.
- Mobile app: Secure keychain (iOS) or Keystore (Android).
- Server-side backend: Process memory or encrypted secret storage.
The traditional “localStorage for the access token” pattern in SPAs is risky for XSS. The current best practice is to keep tokens on the server side and use session cookies for the SPA.
Resource Server Verification
When your API receives a request with an OAuth access token:
Authorization: Bearer <token>
Two strategies:
Introspection
Call the auth server’s /introspect endpoint to validate the token and get its claims.
- Pros: Always current; revocations honored.
- Cons: API call per request.
Self-contained validation (JWT)
If the access token is a JWT, verify the signature locally without calling the auth server.
- Pros: Fast.
- Cons: Revocations not honored until expiration.
For high-throughput resource servers, JWT access tokens with short expirations are typical.
Refresh Tokens
Refresh tokens are long-lived (days to months). When the access token expires, exchange the refresh token for a new access token:
POST /token
grant_type=refresh_token
refresh_token=<token>
client_id=<id>
client_secret=<secret if confidential client>
If the refresh fails (token revoked, expired, or rotated), the user must re-authenticate.
Rotate refresh tokens when used. Issue a new refresh token with each access token; invalidate the old one. This catches token theft — if the attacker uses the refresh token, the legitimate client’s refresh token gets invalidated.
Common Pitfalls
Missing state parameter
CSRF on the callback. Always include and verify.
No PKCE for public clients
SPAs, mobile apps. Without PKCE, intercepted codes are exploitable.
Trusting redirect_uri loosely
Auth servers validate the redirect URI against a configured list. Configure it strictly — don’t allow wildcards.
Long access token expirations
Access tokens are bearer tokens — anyone with the token can use it. Short expirations limit the blast radius.
No HTTPS
Tokens transmitted over HTTP are stolen. Always HTTPS.
Logging tokens
Don’t log access tokens or refresh tokens. They’re credentials.
Confusing OAuth with authentication
OAuth says “this app may access X on behalf of this user.” It doesn’t say “this user is logged in.” Use OIDC (which adds an id_token) for identity.
Implementing OAuth yourself
Don’t. Use well-maintained libraries. The protocol has many subtle security details.
Major OAuth Providers
In 2026, the major providers:
- Google — Comprehensive APIs, mature OAuth.
- Microsoft / Azure AD — Enterprise standard.
- Facebook / Meta — Social login.
- Apple — “Sign in with Apple” (with privacy-preserving relay emails).
- GitHub — Developer-focused.
- Auth0, Okta, Clerk, WorkOS — Identity-as-a-service.
For B2B and enterprise: identity-as-a-service is dominant. For consumer: direct integration with Google/Apple/Facebook.
Choosing a Flow
A simple guide:
- SPA / mobile app: Authorization Code with PKCE.
- Traditional web app: Authorization Code with PKCE.
- Backend service to backend service: Client Credentials.
- CLI tool: Device Code or PKCE.
- TV / IoT: Device Code.
Almost no real use case justifies the deprecated Implicit or Password Credentials flows in 2026.
TL;DR
- OAuth 2.0 = delegated authorization. OIDC = identity on top of OAuth.
- Authorization Code with PKCE is the default for user-facing apps.
- Client Credentials for service-to-service.
- Always use state for CSRF protection.
- Always use PKCE, even for confidential clients.
- Short access tokens + rotated refresh tokens for production.
- Tokens in httpOnly cookies for SPAs; secure storage on mobile/server.
- Don’t implement OAuth yourself. Use libraries.
OAuth is one of those protocols where the surface area is simple (some redirects and tokens) but the security details are extensive. The libraries do most of the work; understanding the flow lets you debug and configure correctly. For the token format underneath (most modern access and ID tokens are JWTs), see JSON Web Tokens explained; for the transport layer, TLS handshake.