Imagine you're building a travel app. You want to let users import their Google Calendar events. The old way: ask users for their Google username and password, store those credentials, and use them to call the Google API on their behalf.
Why storing passwords is catastrophic: If your database gets breached, attackers get full access to all your users' Google accounts โ not just your app. Users have no way to revoke access without changing their entire password. Google has no way to know if it's really you or an attacker.
OAuth 2.0 solves this with delegated authorization: instead of sharing passwords, users grant your app a limited, revocable permission token. The password never leaves Google.
OAuth 2.0 is AUTHORIZATION, not AUTHENTICATION. It answers "what can this app do?" โ not "who is this user?" For identity (authentication), you use OpenID Connect (OIDC), which is a thin layer on top of OAuth 2.0. "Login with Google" actually uses both.
The human who owns the data. You own your Google Calendar. You decide what apps can access it. Without your explicit approval at the consent screen, nothing happens.
The application requesting access to your data. Could be a web app, mobile app, or CLI tool. Registered with the Authorization Server and has a client_id and optionally a client_secret.
Authenticates the user, shows the consent screen, issues tokens. This is accounts.google.com for Google. It's the only party that ever sees your password.
The API that holds your data โ calendar.googleapis.com, gmail.googleapis.com, etc. Accepts access tokens and returns data. Validates tokens against the Authorization Server's public keys.
OAuth 2.0 defines four ways to obtain tokens, called grant types. Each is designed for a different client type and security model.
| Grant Type | Use When | Client Secret? | Status |
|---|---|---|---|
| Authorization Code | Web app with backend server | Yes | RECOMMENDED |
| Auth Code + PKCE | SPA, mobile app, CLI | No | RECOMMENDED |
| Client Credentials | Machine-to-machine (no user) | Yes | USE FOR M2M |
| Implicit | Older SPAs (pre-PKCE) | No | DEPRECATED |
Implicit flow returned the access token directly in the URL fragment (#access_token=ya29...). This leaks the token into browser history, server logs, and the Referer header. PKCE solves the same problem (public client, no secret) without the URL exposure.
This is the main event. A web app with a backend server wants to access the user's Google Calendar. Here's every single HTTP request that happens โ nothing hidden.
User clicks "Sign in with Google". Your backend constructs a URL and sends an HTTP 302 redirect to the browser.
The state value is a random string your server generates and stores in the session. When Google redirects back, you verify it matches. This prevents CSRF attacks where an attacker tricks the user into completing an OAuth flow that the attacker initiates.
Google's server renders its own login page and consent screen. Your app never sees the user's password โ it all happens inside Google's domain. The user sees exactly which permissions they're granting.
Google shows: "MyApp wants access to: Read your calendar events" with Allow/Deny buttons. The scope parameter controls what's shown. Users can revoke this at any time from their Google Account settings.
After consent, Google's server redirects the browser back to your redirect_uri with a short-lived authorization code. This code is single-use and expires in ~10 minutes.
The code travels through the browser (URL bar, browser history, Referer header). It's single-use and expires fast. The real tokens are fetched server-to-server in the next step โ never touching the browser. This is the key security advantage over Implicit flow.
Your server (not the browser) makes a direct POST request to Google's token endpoint. This is a server-to-server call โ the user's browser is not involved.
Google validates the code, client credentials, and PKCE verifier, then returns three tokens.
Every subsequent API call includes the access token in the Authorization header. Google's Resource Server validates the token signature and checks scopes before returning data.
Step through the entire OAuth 2.0 Authorization Code flow as an animated sequence diagram. Watch exactly who sends what to whom.
PKCE (pronounced "pixy") extends Authorization Code flow for public clients โ apps that can't keep a client_secret confidential. That means: single-page apps, mobile apps, and CLI tools.
On mobile, multiple apps can register the same URL scheme (e.g. myapp://callback). A malicious app could intercept the auth code redirect. Without PKCE, the attacker can use that code to get tokens. With PKCE, the code is worthless without the original code_verifier โ which never left the legitimate app.
Add &code_challenge=E9Melhoa...&code_challenge_method=S256 to the authorization URL. Auth Server stores this hash.
Add &code_verifier=dBjftJeZ... to the token exchange POST. Auth Server computes SHA256 of this verifier and checks it matches the stored challenge. If an attacker intercepted the code, they don't have the verifier โ exchange fails.
JSON Web Tokens (JWTs) are the most common format for OAuth 2.0 access tokens and OIDC id_tokens. They're self-contained โ the Resource Server can validate them without calling the Auth Server.
The payload is base64url encoded, not encrypted. Anyone can decode it and read the claims. Never put sensitive data in a JWT payload โ passwords, credit card numbers, PII that shouldn't be shared. The signature only prevents tampering, not reading.
Paste any JWT below to decode and inspect it in real time. The decoder runs entirely in your browser โ nothing is sent to a server.
Authorization: Bearer header on every API call/token endpoint| Storage | XSS Risk | CSRF Risk | Recommendation |
|---|---|---|---|
HttpOnly Cookie | None (JS can't read) | Yes (need SameSite=Strict) | BEST |
localStorage | High (XSS reads it) | None | AVOID |
sessionStorage | High (XSS reads it) | None | MEDIUM |
| In-memory (JS variable) | Medium (loses on refresh) | None | ACCEPTABLE for SPAs |
OAuth 2.0 handles authorization (what can this app do). But "Login with Google" also needs authentication (who is this user). That's OpenID Connect โ a thin identity layer built on top of OAuth 2.0.
Answers: "What can this app access?"
Returns: access_token (for API calls)
Example: "This app can read your calendar."
Answers: "Who is this user?"
Returns: id_token (identity JWT)
Example: "This user is ali@example.com, Google user ID 112345."
To trigger OIDC, include openid in the scope. The token response then includes an id_token โ a JWT containing identity claims (sub, email, name, etc.).
Scope: openid email profile
Returns: access_token (to call Google APIs) + id_token (to know who logged in)
Your backend: verifies id_token signature, extracts sub (stable user ID) + email, creates/updates your user record.
state ParameterIf you don't check that state matches what you sent, an attacker can craft an authorization URL, trick a victim into clicking it, and then inject their own callback URL with a legitimate code. Always validate state as a CSRF token.
redirect_uri ExactlyIf the Auth Server does prefix matching (e.g. allows any https://myapp.com/...), an attacker can register a subdomain and steal codes. Auth Servers must do exact matching of pre-registered redirect URIs. Register only specific full URIs, not wildcards.
aud ClaimA JWT issued for Client A has "aud": "client_a_id". If Client B accepts tokens without verifying aud, an attacker with a token from Client A can use it at Client B. Always verify the aud claim equals your own client_id.
SPAs and mobile apps that use Authorization Code flow without PKCE are vulnerable to authorization code interception. Always use PKCE for any client that can't keep a secret. Some Auth Servers now require it for all clients.
If you log all HTTP requests (common in debugging), access tokens in Authorization headers end up in logs. Sanitize or redact Authorization headers before logging. Never put tokens in URL query parameters in production.
When a backend service needs to call another backend service โ no user involved โ use Client Credentials flow. A cron job calling an internal API, a microservice calling another microservice, a CI/CD pipeline uploading artifacts.
| Flow | Client Type | Secret? | User Involved? | Returns | Use Case |
|---|---|---|---|---|---|
| Auth Code | Web (backend) | Yes | Yes | access + refresh + id | Traditional web apps |
| Auth Code + PKCE | SPA / Mobile | No | Yes | access + refresh + id | React, React Native, iOS |
| Client Credentials | Backend service | Yes | No | access only | Microservices, cron jobs |
| Implicit (deprecated) | SPA (old) | No | Yes | access only (in URL) | Don't use |
| Claim | Full Name | Type | What to Check |
|---|---|---|---|
iss | Issuer | string (URL) | Must match expected Auth Server URL |
sub | Subject | string (user ID) | Stable unique user identifier |
aud | Audience | string or array | Must include your client_id |
exp | Expiration | Unix timestamp | Must be in the future |
iat | Issued At | Unix timestamp | Should be recent (clock skew) |
jti | JWT ID | string | Check for token replay attacks |
state (CSRF)aud claimexp claimAuth Servers publish public keys at: