Working with OAuth 2.0

1. Understanding OAuth 2.0 Flow

RoleDescription
Resource OwnerUser who grants access
ClientApp requesting access
Authorization ServerIssues tokens after consent
Resource ServerAPI 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

AspectDetail
StatusDEPRECATED (OAuth 2.1)
Replaced byAuthorization Code + PKCE
Why removedTokens in URL fragment leak via referrer/history

4. Understanding Client Credentials Flow

Use CaseM2M (no user)
RequestPOST /token grant_type=client_credentials
AuthBasic auth (client_id:client_secret) or mTLS/JWT assertion
Responseaccess_token only (no refresh)
ScopePre-provisioned to client

5. Understanding Resource Owner Password Flow

AspectDetail
StatusDEPRECATED (OAuth 2.1)
Why removedClient handles user password — breaks delegation model
ReplacementAuth 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"
});
FieldRequired For
code_verifier43–128 chars, sent at token exchange
code_challengeBASE64URL(SHA256(verifier))
code_challenge_methodS256 (never plain)

7. Registering OAuth Clients

FieldPurpose
client_idPublic identifier
client_secretOnly for confidential clients
redirect_urisExact-match allowlist
grant_typesAllowed grants per client
scopesPermitted scopes
token_endpoint_auth_methodclient_secret_basic / private_key_jwt / mTLS / none

8. Requesting Authorization

ParameterRequired
response_typecode
client_idYes
redirect_uriYes (exact match)
scopeYes
stateRecommended (CSRF protection)
nonceRequired for OIDC
code_challengeRequired (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

ParameterValue
grant_typerefresh_token
refresh_tokenThe token
scopeOptional — narrower than original
Public clientsMUST use rotation

12. Understanding OAuth Scopes

PatternExample
Resource actionread:orders, write:profile
Hierarchicalorders.read, orders.write
OIDC standardopenid profile email address phone offline_access
GranularPer resource type — avoid admin blanket scope

13. Validating Redirect URIs

RuleWhy
Exact matchNo wildcards / substring
No fragmentsRFC 6749
HTTPS only (except localhost)Prevent token interception
MobileUniversal Links / App Links preferred over custom schemes
NativeLoopback redirect (http://127.0.0.1:<port>)

14. Implementing State Parameter

PurposeImplementation
CSRF protectionRandom per-request value
StorageSession / cookie before redirect
ValidationConstant-time compare on callback
Carry contextEncode return URL (signed)