Skip to main content

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)

Was this article helpful?