What OAuth Is (And Isn't)
OAuth 2.0 is an authorization protocol. It lets a user grant a third-party application access to their data on another service, without sharing their password.
Common confusion: OAuth is not authentication. It doesn't tell you who the user is. It tells you that someone with permission to do X has authorized your app to do X on their behalf. OpenID Connect (OIDC) is a thin layer on top of OAuth that adds authentication.
The Four Roles
Resource Owner: the user. Owns the data being accessed.
Client: the app requesting access (your app, e.g., a calendar app wanting to read Gmail).
Authorization Server: issues access tokens. Run by the data owner (Google, GitHub).
Resource Server: hosts the user's data and accepts access tokens. Often the same company as the auth server.
The Authorization Code Flow
The most common flow. Used for web apps with a backend.
1. User clicks "Sign in with Google" in your app.
2. Your app redirects them to Google's auth server with a bunch of parameters: client_id, scope, redirect_uri.
3. User logs into Google (if not already) and approves your app's request.
4. Google redirects back to your app's redirect_uri with a short-lived authorization code.
5. Your app's backend exchanges the code (plus a client_secret) for an access token.
6. Your app uses the access token to call Google's APIs on the user's behalf.
Why this dance? The access token never touches the user's browser, so it can't be stolen via JavaScript injection. The code is short-lived and one-time-use. The client_secret is on your backend, never exposed.
Other Flows
Authorization Code with PKCE: for apps that can't keep a client_secret (mobile, single-page apps). Uses a dynamic code verifier instead. The modern recommendation for all flows including web apps.
Client Credentials: for server-to-server with no user. Your backend authenticates as itself to call an API.
Device Code: for devices without browsers (TVs, CLI tools). Display a code, ask user to enter it on their phone.
Implicit: deprecated. Don't use.
Resource Owner Password Credentials: deprecated. Don't use.
Tokens
Two main token types:
Access Token: short-lived (typically 1 hour). Sent with API requests. The resource server validates it on every call.
Refresh Token: long-lived (days to forever). Used to get a new access token without re-prompting the user.
Access tokens are usually JWTs (signed JSON) but can be opaque strings. Either way, the client doesn't typically inspect them.
Scopes
Permissions are scoped. When you request access, you list scopes: read:email, write:calendar, read:profile. The user sees these on the consent screen and approves or denies.
Best practice: ask for the minimum scopes you need. Asking for too much causes users to abandon the flow or distrust your app.
Common Mistakes
Storing access tokens in localStorage: XSS attacks can steal them. Use httpOnly cookies or memory.
Not validating redirect_uri: if your auth server accepts any redirect_uri, attackers can capture codes.
Skipping PKCE for SPAs: required for security.
Long-lived access tokens: if leaked, attackers have long access. Keep them short.
Trusting the access token contents without verification: always validate the JWT signature.
OpenID Connect
OAuth alone doesn't authenticate the user. OIDC adds an ID Token (a JWT) that contains the user's identity (email, name, sub claim). When you "Sign in with Google," you're using OIDC under the hood.
If all you need is "log this user in," use OIDC. If you also need to call APIs on the user's behalf, you need OAuth (often via OIDC).
The One Thing to Remember
OAuth 2.0 is delegated authorization, not authentication. Its purpose is to let an app access a user's data on another service without ever seeing the password. The Authorization Code flow with PKCE is the right default for nearly every modern app. Use OIDC if you need to know who the user is. Don't roll your own; use a library or identity provider.