Working with OAuth 2.0
1. Understanding OAuth 2.0 Flow
| Role | Description |
|---|---|
| Resource Owner | User who grants access |
| Client | App requesting access |
| Authorization Server | Issues tokens after consent |
| Resource Server | API that hosts protected resources |
User-Agent ─→ Client ─→ Authorization Server ─→ Resource Server
(consent + token issuance) (uses access token)
2. Understanding Authorization Code Flow
1. /authorize?response_type=code&client_id&redirect_uri&state&scope&code_challenge
2. User authenticates & consents
3. AS redirects → redirect_uri?code=AUTH_CODE&state
4. Client POST /token (code, code_verifier, client_id, redirect_uri)
5. AS returns {access_token, refresh_token, id_token}
6. Client calls API with Bearer token
3. Understanding Implicit Flow
| Aspect | Detail |
|---|---|
| Status | DEPRECATED (OAuth 2.1) |
| Replaced by | Authorization Code + PKCE |
| Why removed | Tokens in URL fragment leak via referrer/history |
4. Understanding Client Credentials Flow
| Use Case | M2M (no user) |
|---|---|
| Request | POST /token grant_type=client_credentials |
| Auth | Basic auth (client_id:client_secret) or mTLS/JWT assertion |
| Response | access_token only (no refresh) |
| Scope | Pre-provisioned to client |
5. Understanding Resource Owner Password Flow
| Aspect | Detail |
|---|---|
| Status | DEPRECATED (OAuth 2.1) |
| Why removed | Client handles user password — breaks delegation model |
| Replacement | Auth Code + PKCE (even for first-party) |
6. Implementing Authorization Code with PKCE
Example: PKCE generation (browser)
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
);
sessionStorage.setItem("pkce_v", verifier);
location.href = `${AS}/authorize?` + new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: REDIRECT,
scope: "openid profile email",
state: crypto.randomUUID(),
code_challenge: challenge,
code_challenge_method: "S256"
});
| Field | Required For |
|---|---|
| code_verifier | 43–128 chars, sent at token exchange |
| code_challenge | BASE64URL(SHA256(verifier)) |
| code_challenge_method | S256 (never plain) |
7. Registering OAuth Clients
| Field | Purpose |
|---|---|
| client_id | Public identifier |
| client_secret | Only for confidential clients |
| redirect_uris | Exact-match allowlist |
| grant_types | Allowed grants per client |
| scopes | Permitted scopes |
| token_endpoint_auth_method | client_secret_basic / private_key_jwt / mTLS / none |
8. Requesting Authorization
| Parameter | Required |
|---|---|
| response_type | code |
| client_id | Yes |
| redirect_uri | Yes (exact match) |
| scope | Yes |
| state | Recommended (CSRF protection) |
| nonce | Required for OIDC |
| code_challenge | Required (OAuth 2.1) |
9. Exchanging Authorization Code for Token
Example: Token exchange
const res = await fetch(`${AS}/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT,
client_id: CLIENT_ID,
code_verifier: sessionStorage.getItem("pkce_v")
})
});
const { access_token, refresh_token, id_token } = await res.json();
10. Using Access Tokens for API Calls
Example: Authorized request
fetch("https://api.example.com/orders", {
headers: { "Authorization": `Bearer ${access_token}` }
});
11. Implementing Token Refresh Flow
| Parameter | Value |
|---|---|
| grant_type | refresh_token |
| refresh_token | The token |
| scope | Optional — narrower than original |
| Public clients | MUST use rotation |
12. Understanding OAuth Scopes
| Pattern | Example |
|---|---|
| Resource action | read:orders, write:profile |
| Hierarchical | orders.read, orders.write |
| OIDC standard | openid profile email address phone offline_access |
| Granular | Per resource type — avoid admin blanket scope |
13. Validating Redirect URIs
| Rule | Why |
|---|---|
| Exact match | No wildcards / substring |
| No fragments | RFC 6749 |
| HTTPS only (except localhost) | Prevent token interception |
| Mobile | Universal Links / App Links preferred over custom schemes |
| Native | Loopback redirect (http://127.0.0.1:<port>) |
14. Implementing State Parameter
| Purpose | Implementation |
|---|---|
| CSRF protection | Random per-request value |
| Storage | Session / cookie before redirect |
| Validation | Constant-time compare on callback |
| Carry context | Encode return URL (signed) |