OAuth 2.x — Delegated Authorization
A protocol for letting an application act on a user's behalf against a third-party API without ever holding the user's password — which is a different problem from logging users into your own site.
Frame the problem
Security starts with a concrete asset, attacker capability and trust crossing.
The problem it solves
Before OAuth, an application that wanted to read your repositories asked for your username and password. That gave it everything — permanent, unlimited, unrevocable-except-by-password-change access to your entire account, at a third party you may or may not trust. The pattern was so bad it had a name: the password anti-pattern.
OAuth replaces it with delegation. The user is sent to the service that holds the data, authenticates there (so the client never sees the credential), approves a *specific* scope of access, and the client receives a token that grants exactly that scope, for a limited time, revocable independently of the password.
The four roles are worth naming precisely, because most confusion about OAuth is confusion about which party is doing what. The resource owner is the user. The client is the application asking for access. The authorization server authenticates the user and issues tokens. The resource server is the API that accepts those tokens. The authorization server and the resource server are often run by the same company, which hides the distinction and makes the protocol harder to reason about than it is.
OAuth is not login
This is the most consequential misunderstanding in the protocol, and it produces real vulnerabilities rather than merely inelegant designs.
An OAuth access token proves that *some client* was granted access to *some resource*. It does not prove who the user is, and it is not addressed to you. A pattern that appears repeatedly: the client receives an access token, calls the provider's "get my profile" endpoint, receives a user id, and logs that user in. The flaw is that a token obtained by a *different* client — a malicious application the user also authorized — will also work against that endpoint and return the same user id. Without a token that is bound to your application and verifiable as such, you are authenticating on the basis of a bearer token you did not issue and cannot validate the audience of.
The correct construction for login is OpenID Connect, which adds an ID token: a JWT issued *to your client* (with your client id as aud), signed by the provider, containing verified identity claims and a nonce that binds it to your specific authorization request. See OpenID Connect.
The short version worth remembering: OAuth answers "may this app do this on the user's behalf?"; OIDC answers "who is this user?". Using the first to answer the second is a category error with security consequences.
Scopes, consent and tokens
Scopes are the unit of delegation and their design determines whether least privilege is achievable at all. A provider offering repo as a single scope covering read and write across every repository forces every client that needs to read one file to request write access to everything. Good scope design is fine-grained and composable — repos:read, repos:write, issues:write — and clients should request the minimum and expand only when a feature needs it.
Consent is where the user makes the decision, and it is only meaningful if the screen is honest. "This application will be able to read and write all your repositories" is a decision; "Connect your account" is not. As a client, request narrow scopes so the consent screen is reassuring; as a resource owner, read it.
Tokens come in two kinds with different properties. The access token is short-lived and presented to the resource server on each call; it should be minutes to hours, not months. The refresh token is long-lived, held only by the client, and exchanged for new access tokens; it must be stored as securely as a password and revoked when the user disconnects. Refresh token rotation — issuing a new refresh token on each use and invalidating the old one — is the standard defence, because reuse of a rotated token indicates theft and can trigger revocation of the whole grant.
Finally, revocation must be real and user-visible. A user disconnecting an application should terminate its access immediately, and the provider should offer a page listing authorized applications and their scopes. From the client side, holding a refresh token indefinitely for an integration the user stopped using is an unnecessary liability.
| Access token | Refresh token | ID token (OIDC) | |
|---|---|---|---|
| Purpose | Call the resource API | Obtain new access tokens | Tell the client who the user is |
| Audience | Resource server | Authorization server | The client, specifically |
| Lifetime | Minutes to an hour | Days to months, rotated | Seconds — consumed once at login |
| Sent to the API? | Yes | Never | Never |
| Storage | Memory; never in the browser if avoidable | Server-side, encrypted, like a password | Not stored — verified and discarded |
| Revocation | Expiry, or introspection | Explicit revoke; rotate on every use | N/A |
Key points
- OAuth is delegated authorization: an app acts on the user's behalf without ever seeing their credential.
- Four roles — resource owner, client, authorization server, resource server — and most confusion comes from the last two being the same company.
- An access token is not proof of identity. Using it for login is a category error; use OIDC ID tokens instead.
- Scope design decides whether least privilege is possible; request the minimum and expand per feature.
- Access tokens short, refresh tokens rotated and stored like passwords, revocation immediate and user-visible.
Boundary control exercise
This lesson uses the shared boundary-control exercise.
Follow the attack
Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.
- 1Attacker → build or compromise a client that the user authorizes for some benign purpose.
- 2Client → request broad scopes that the consent screen presents vaguely.
- 3Granted token → use it against the resource server for everything the scope permits.
- 4Alternatively → obtain a token issued to a different client and present it to a site that authenticates users by calling a profile endpoint.
- Over-broad scopes give a third party persistent access to far more than the feature required.
- Token-as-login flaws allow account takeover on the relying site using a token obtained elsewhere.
- Refresh tokens stored insecurely by a client are long-lived credentials to another company's data.
Defend, detect, recover
One prevention is a single point of security failure. Layer it and make failure observable.
- • Use OIDC for authentication and OAuth for API access; never conflate them.
- • Request the narrowest scopes; add incremental authorization as features need it.
- • Rotate refresh tokens on every use and revoke the grant when a rotated token is reused.
- • Store refresh tokens server-side and encrypted; never place them in a browser.
- • Alert on reuse of a rotated refresh token — a strong theft indicator.
- • Alert on a client suddenly using scopes it has not exercised before.
- • Give users a visible list of connected applications and last-used timestamps.
- • Revoke the grant, not just the access token; the refresh token is the durable credential.
- • Notify the user which application was revoked and what it could access.
- • For a compromised client, revoke every grant issued to that client id.
- • Users approve consent screens without reading them, so scope design carries most of the protection.
- • A compromised client holds valid grants for every user who authorized it.
- • Provider scope granularity is outside your control and is frequently too coarse for real least privilege.