Never Share Your Password Again - The OAuth 2.0 Solution
Before OAuth 2.0 existed, if you wanted a third-party app to read your Twitter timeline, you had to give them your Twitter username and password. Not a token, not a scoped credential - your actual password.
The app would log in as you, do what it needed, and (if you were lucky) not store the password anywhere sketchy. Users did this constantly. Whole categories of app worked this way: Twitter clients, mail readers, contact importers, calendar syncers. Every one of them held a growing collection of user passwords.
This was called the password anti-pattern and it was as bad as it sounds. If the third party app got breached, every user account it touched went with it. Users could not revoke access to a single app without changing their password everywhere. The app had full account access, not just the piece it actually needed.
What OAuth 2.0 Does
The user authenticates only with the service they trust (Google, GitHub, etc.). The third-party app never sees the password. The service issues the app a token, and the token carries three important properties:
- Scoped - It grants only the specific permissions the user approved.
- Time-limited - It expires, usually within an hour.
- Revocable - The user can kill it any time from their account settings.
The Actual Flow
The modern standard is Authorization Code with PKCE. Five steps.
- The app redirects the user to the authorization server.
https://auth.example.com/oauth/authorize
?response_type=code
&client_id=budgettrack-prod
&redirect_uri=https://budgettrack.com/callback
&scope=calendar.read
&state=xY7f3aQ2
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
The user authenticates on the auth server's own domain. The third-party app never sees this happen. No password crosses the network to anyone but the auth server itself.
The auth server redirects back with a short-lived code.
https://budgettrack.com/callback
?code=SplxlOBeZQQYbYS6WxSbIA
&state=xY7f3aQ2
- The app exchanges the code for a token, server-to-server.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://budgettrack.com/callback
&client_id=budgettrack-prod
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
- The auth server returns the token.
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "v1.MRn7...",
"scope": "calendar.read"
}
The app now calls the API with Authorization: Bearer until the token expires. The user's password was never involved at any point.
What Those Magic Parameters Actually Do
The flow above has four security-critical parameters that most tutorials gloss over. Here is what each one is defending against.
state=xY7f3aQ2 - CSRF protection. The app generates a random value, sends it in the authorize request, and checks that the same value comes back on the redirect. Without this, an attacker could trick a signed-in user into completing the attacker's OAuth flow, binding the attacker's account to the user's app session. Not checking state is one of the most common OAuth bugs in the wild.
code_challenge + code_verifier - PKCE (Proof Key for Code Exchange). The app generates a random code_verifier, hashes it with SHA-256 to produce the code_challenge sent in step 1, then presents the original code_verifier when exchanging the code in step 4. This defeats any attacker who intercepts the authorization code - without the matching code_verifier, the stolen code is worthless. Required for mobile apps and SPAs; strongly recommended everywhere else.
redirect_uri=https://budgettrack.com/callback - The auth server will only send the code to this exact URL, pre-registered with the auth server. Attackers cannot swap in https://evil.com/callback to hijack the code mid-flight.
Short-lived code (step 3) - The authorization code is single-use and expires in seconds (typically 10 seconds to 2 minutes). Even if captured in a log or network trace, it cannot be reused later.
Four small parameters. Each one closes a specific attack. Together they are the reason OAuth 2.0 is secure enough to run every "Sign in with Google" button on the internet.
Why Scopes Matter
scope=calendar.read means the app can read the calendar. That is it. It cannot delete events, cannot send email, cannot access contacts, cannot post on the user's behalf.
If the app gets breached tomorrow, the attacker has read access to calendars. Bad, but bounded. Compare this to the pre-OAuth world where the attacker would have had full account access to every service the app touched, everywhere.
The whole game of security is turning "everything" into "one specific thing," and scopes are how OAuth 2.0 does it at the protocol level.
Summary
OAuth 2.0 exists so a user can grant a third-party app scoped, time limited, revocable access to their account without ever handing over their password. Everything else in the protocol - code exchanges, refresh tokens, PKCE, scopes, discovery - is machinery in service of that one principle.
Comments
No comments yet. Start the discussion.