OAuth 2.0 is the industry-standard authorization framework that underpins nearly every modern API, mobile app, and single-page application. Yet even experienced developers struggle with choosing the right flow, securing tokens, and understanding where OAuth ends and OpenID Connect begins. This guide consolidates everything you need to know about OAuth 2.0 into a single reference, with links to deep-dive articles for each topic.

Whether you are building a React SPA, a microservice mesh, or a mobile application, by the end of this guide you will understand how every piece of the OAuth ecosystem fits together and which patterns to apply in your specific architecture.

What Is OAuth 2.0

OAuth 2.0 (RFC 6749) is a delegation protocol that allows a user to grant a third-party application limited access to a resource without sharing their credentials. It replaced OAuth 1.0’s complex signature mechanism with bearer tokens transmitted over TLS.

The Four Actors

Every OAuth 2.0 interaction involves four roles:

ActorDescriptionExample
Resource OwnerThe entity that owns the dataEnd user
ClientThe application requesting accessReact SPA, mobile app
Authorization ServerIssues tokens after authenticating the resource ownerKeycloak, Auth0, Okta
Resource ServerHosts the protected APIYour backend REST API

Grant Types at a Glance

OAuth 2.0 defines several grant types (also called “flows”). Each serves a different architecture:

  • Authorization Code – The most secure flow for user-facing apps. The client receives an authorization code via a browser redirect and exchanges it for tokens at the token endpoint. For a step-by-step walkthrough, see Understanding Authorization Code Flow.
  • Authorization Code with PKCE – Extends the authorization code flow with a cryptographic proof, required for public clients. See Authorization Code Flow with PKCE.
  • Client Credentials – For machine-to-machine communication where no user is involved.
  • Refresh Token – Not a standalone flow but a mechanism to obtain new access tokens silently.
  • Implicit (deprecated) – Previously used for SPAs; replaced by Authorization Code with PKCE.
  • Resource Owner Password Credentials (deprecated) – Anti-pattern that exposes user credentials directly to the client.

For a comparison of the two most common flows, see our article on OAuth 2.0 Best Practices.

OAuth 2.0 vs OpenID Connect

One of the most common sources of confusion is the relationship between OAuth 2.0 and OpenID Connect (OIDC). They are complementary, not competing, protocols.

OAuth 2.0 answers the question: “What is this application allowed to do?” It is an authorization framework. It issues access tokens that grant permission to call APIs, but it says nothing about who the user is.

OpenID Connect answers the question: “Who is this user?” It is an authentication layer built on top of OAuth 2.0. It adds:

  • An ID token (a JWT containing identity claims like sub, email, name)
  • A UserInfo endpoint for fetching additional profile data
  • A Discovery document (.well-known/openid-configuration) for automatic client configuration
  • Standard scopes (openid, profile, email, address, phone)
OOAAuCuCtltlhihiee2n2n.t.t00a+loOnAIAeuDu:tCth:hoorriizzaattiioonnSSeerrvveerrAAcccceessssTTookkeenn+IDReTsookuernceSerRveesrourceServer+Identity

When to use which:

  • If you only need to call an API on behalf of a user (e.g., read their calendar), OAuth 2.0 is sufficient.
  • If you need to log the user in and know their identity, use OpenID Connect.
  • If you need both, use OIDC – it automatically includes OAuth 2.0 capabilities.

For a detailed comparison, read OAuth 2.0 vs OIDC. For a broader protocol comparison that includes SAML, see SAML vs OIDC and SAML, OIDC, OAuth Deep Dive.

Authorization Code Flow with PKCE

The Authorization Code Flow with PKCE (Proof Key for Code Exchange, pronounced “pixy”) is the recommended flow for all user-facing applications in 2026. OAuth 2.1 mandates PKCE for every client type.

How It Works

  1. The client generates a random code_verifier (43-128 characters) and derives a code_challenge using SHA-256.
  2. The client sends the user to the authorization endpoint with the code_challenge.
  3. The user authenticates and consents.
  4. The authorization server redirects back with an authorization_code.
  5. The client exchanges the code at the token endpoint, including the original code_verifier.
  6. The authorization server verifies SHA256(code_verifier) == code_challenge before issuing tokens.

Authorization Request

GET /authorize?
  response_type=code
  &client_id=my-spa
  &redirect_uri=https://app.example.com/callback
  &scope=openid profile email
  &state=abc123
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
HTTP/1.1
Host: auth.example.com

Token Exchange

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://app.example.com/callback
&client_id=my-spa
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Token Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid profile email"
}

Why PKCE Matters

Without PKCE, an attacker who intercepts the authorization code (through a malicious browser extension, a compromised redirect URI, or an OS-level custom scheme handler on mobile) can exchange it for tokens. PKCE ensures only the client that initiated the request can complete the exchange.

For implementation details, see How PKCE Enhances Security and try our interactive PKCE Generator Tool.

Client Credentials Flow

The Client Credentials Flow is designed for server-to-server communication where no user is involved. The client authenticates directly with the authorization server using its own credentials (client ID and client secret) and receives an access token.

When to Use

  • Microservice-to-microservice calls
  • Batch jobs and cron tasks
  • Backend services accessing third-party APIs
  • CI/CD pipelines that need API access

Token Request

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic bXlDbGllbnRJZDpteUNsaWVudFNlY3JldA==

grant_type=client_credentials
&scope=api:read api:write

Token Response

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIiwic3ViIjoibXlDbGllbnRJZCIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwNzg5MjAwMCwic2NvcGUiOiJhcGk6cmVhZCBhcGk6d3JpdGUifQ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "api:read api:write"
}

Note that no refresh_token or id_token is returned – there is no user to refresh on behalf of, and no identity to assert.

Security Considerations

  • Never expose client secrets in front-end code, mobile apps, or public repositories.
  • Rotate secrets regularly and use short-lived access tokens.
  • Limit scopes to the minimum required for the service.
  • Consider mTLS client authentication (RFC 8705) for high-security environments instead of shared secrets.

For a comprehensive walkthrough, read Client Credentials Flow.

Refresh Token Management

Access tokens are intentionally short-lived (typically 5-60 minutes). Refresh tokens allow clients to obtain new access tokens without forcing the user to re-authenticate.

The Refresh Flow

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
&client_id=my-spa

The authorization server responds with a new access token (and optionally a new refresh token):

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9.new-payload...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "dGhpc0lzQU5ld1JlZnJlc2hUb2tlbg"
}

Refresh Token Rotation

For public clients (SPAs, mobile apps) that cannot keep a client secret, refresh token rotation is a critical security measure. With rotation:

  1. Each time the client uses a refresh token, the authorization server issues a new refresh token and invalidates the old one.
  2. If an attacker steals and uses the old refresh token, the authorization server detects the reuse and revokes the entire token family.
  3. The legitimate user is forced to re-authenticate, but the attacker is locked out.
NAotrRRtRmTTaTa--c-l12k1efrlorwe:AAuDTTsE--eN23sIE++RDTRR-(TT1r--:e23use((RRdTTe--t12eciitnnevvdaa,lliiaddlaaltteetddo))kensrevoked)

Best Practices

  • Always use rotation for public clients.
  • Set absolute lifetime limits on refresh tokens (e.g., 7-30 days).
  • Implement idle timeout – revoke if unused for a period.
  • Store securely: HttpOnly cookies for web apps, secure storage for mobile.
  • Revoke on logout – call the revocation endpoint when the user signs out.

For implementation examples in Java, see Refresh Tokens in OAuth 2.0.

JWT Tokens: Structure, Validation, and Security

JSON Web Tokens (JWTs) are the most common format for OAuth 2.0 access tokens and OIDC ID tokens. Understanding their structure is essential for secure token handling.

JWT Structure

A JWT consists of three Base64URL-encoded parts separated by dots:

header.payload.signature

Header – specifies the algorithm and token type:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-2026-02"
}

Payload – contains the claims:

{
  "iss": "https://auth.example.com",
  "sub": "user-12345",
  "aud": "https://api.example.com",
  "exp": 1707892000,
  "iat": 1707888400,
  "scope": "openid profile email",
  "email": "[email protected]",
  "name": "Jane Developer"
}

Signature – created by signing the header and payload with the authorization server’s private key:

RSAbpSarHsiAev26a54t6Ue(rKleEyncode(header)+"."+base64UrlEncode(payload),

Token Validation Checklist

Every resource server that receives a JWT must validate it before trusting the claims. Here is the minimum validation:

  1. Decode the token and parse the header, payload, and signature.
  2. Verify the signature using the authorization server’s public key (fetched from the JWKS endpoint).
  3. Check iss (issuer) – must match your expected authorization server.
  4. Check aud (audience) – must include your API’s identifier.
  5. Check exp (expiration) – reject if the token is expired. Allow a small clock skew (30-60 seconds).
  6. Check iat (issued at) – optionally reject tokens issued too far in the past.
  7. Check nbf (not before) – reject if the token is not yet valid.
  8. Validate scopes – ensure the token grants the permissions required for the requested operation.
import jwt
import requests

# Fetch the JWKS from the authorization server
jwks_url = "https://auth.example.com/.well-known/jwks.json"
jwks = requests.get(jwks_url).json()

# Decode and validate the token
try:
    payload = jwt.decode(
        token,
        jwks,                          # Public keys
        algorithms=["RS256"],          # Expected algorithm
        audience="https://api.example.com",
        issuer="https://auth.example.com",
        options={"require": ["exp", "iss", "aud", "sub"]}
    )
    print(f"Authenticated user: {payload['sub']}")
except jwt.ExpiredSignatureError:
    print("Token has expired")
except jwt.InvalidAudienceError:
    print("Token audience mismatch")
except jwt.InvalidIssuerError:
    print("Token issuer mismatch")

Common JWT Pitfalls

  • Never trust the alg header blindly. Always enforce the expected algorithm on the server side to prevent algorithm confusion attacks.
  • Never use "alg": "none". This disables signature verification entirely.
  • Never store JWTs in localStorage. They become vulnerable to XSS. Use HttpOnly cookies instead.
  • Never put sensitive data in the payload. JWTs are Base64-encoded, not encrypted. Anyone can read the claims.

For a beginner-friendly introduction, read What is a JWT. For production validation patterns, see JWT Decoding and Validation. You can also inspect tokens interactively with our JWT Decode Tool and construct test tokens with the JWT Builder Tool.

OAuth for Single-Page Applications

SPAs present unique security challenges because they run entirely in the browser – there is no server-side component to store secrets or proxy token requests. The OAuth community has converged on two patterns.

Pattern 1: Authorization Code with PKCE (Direct)

The SPA performs the OAuth flow directly with the authorization server using PKCE. Tokens are stored in memory (not localStorage) and refreshed using refresh token rotation with short-lived refresh tokens.

Pros:

  • Simpler architecture, no backend proxy needed
  • Works well for APIs on the same domain

Cons:

  • Tokens are exposed to JavaScript (XSS risk)
  • Refresh tokens in the browser require strict rotation and short lifetimes
  • Cross-origin issues with third-party cookies being blocked

For an implementation guide, see PKCE in SPAs.

Pattern 2: Backend-for-Frontend (BFF)

The BFF pattern introduces a thin backend proxy that handles the OAuth flow on behalf of the SPA. The proxy stores tokens server-side and issues a session cookie to the SPA.

Browser(SGSGCJPEeEoSATtToO)-kN/C/iboaerfop:efkis/i/spledeoo:asngtssisaieneosnsionBFFBackAATTGAJeuuooEuSnttkkTtOdhheehN)oonn/orrsarriiEpiezzx(izsaacs/aptthtdtoiiaoainoonrtosnngeaneed:RC)eoBqdeueaerAseutrthServer

Pros:

  • Tokens never reach the browser – immune to XSS token theft
  • Works with third-party cookie restrictions
  • Supports confidential clients (client secret on the server)

Cons:

  • Requires a backend service
  • Adds latency from the extra hop

The BFF pattern is recommended by the OAuth Working Group for SPAs that need high security. For a React implementation, see OAuth BFF for React SPA.

Which Pattern to Choose

CriteriaPKCE DirectBFF
Token exposure to JSYesNo
Requires backendNoYes
Third-party cookie issuesPossibleNone
Suitable for high-security appsWith caveatsYes
Implementation complexityLowerHigher

For most production applications handling sensitive data, the BFF pattern is the safer choice.

OAuth 2.1: What Is Changing

OAuth 2.1 (draft RFC) consolidates best practices from years of OAuth 2.0 usage into the core specification. It does not introduce new concepts; rather, it makes existing security recommendations mandatory.

Key Changes from OAuth 2.0

  1. PKCE is mandatory for all authorization code grants, including confidential clients.
  2. Implicit grant is removed. SPAs must use Authorization Code with PKCE.
  3. Resource Owner Password Credentials (ROPC) grant is removed. Applications must not collect user passwords.
  4. Refresh tokens must be sender-constrained or use rotation for public clients.
  5. Redirect URIs must use exact string matching. Wildcard and pattern matching are no longer allowed.
  6. Bearer tokens in query strings are prohibited. Tokens must be sent in the Authorization header or POST body.

Migration Checklist

If you are currently running OAuth 2.0, here is what to update:

  • Add PKCE to all authorization code flows (even confidential clients)
  • Remove any Implicit grant configurations
  • Remove any ROPC grant configurations
  • Enable refresh token rotation for public clients
  • Audit redirect URIs for exact matching
  • Ensure tokens are not passed in query parameters
  • Update client libraries to the latest versions

Example: Adding PKCE to a Confidential Client

Even if your server-side application already uses a client secret, OAuth 2.1 requires PKCE. Here is a Node.js example:

const crypto = require('crypto');

// Generate PKCE values
function generatePKCE() {
  const verifier = crypto.randomBytes(32)
    .toString('base64url');
  const challenge = crypto.createHash('sha256')
    .update(verifier)
    .digest('base64url');
  return { verifier, challenge };
}

const { verifier, challenge } = generatePKCE();

// Include in authorization request
const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', 'my-confidential-app');
authUrl.searchParams.set('redirect_uri', 'https://app.example.com/callback');
authUrl.searchParams.set('scope', 'openid profile');
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', crypto.randomBytes(16).toString('hex'));

// Store verifier in session for token exchange
session.pkceVerifier = verifier;

For the full OAuth 2.1 breakdown, read OAuth 2.1 Complete Guide.

Security Best Practices

OAuth security is not just about choosing the right flow. The details of your implementation determine whether your system is truly secure. Here are the practices that matter most.

Use State to Prevent CSRF

The state parameter ties the authorization request to the user’s browser session. Without it, an attacker could craft a malicious authorization URL and trick the user into linking the attacker’s account.

// Generate state and store in session
const state = crypto.randomBytes(16).toString('hex');
session.oauthState = state;

// Verify on callback
if (req.query.state !== session.oauthState) {
  throw new Error('State mismatch -- possible CSRF attack');
}

Use Nonce to Prevent Replay Attacks

The nonce parameter (used with OIDC) prevents ID token replay attacks. The authorization server includes the nonce in the ID token, and the client verifies it matches the value sent in the original request.

// Include nonce in authorization request
const nonce = crypto.randomBytes(16).toString('hex');
session.oidcNonce = nonce;

// After receiving the ID token, verify
const decoded = jwt.decode(idToken);
if (decoded.nonce !== session.oidcNonce) {
  throw new Error('Nonce mismatch -- possible replay attack');
}

Combine State, Nonce, and PKCE

For maximum security, use all three parameters together:

ParameterProtects AgainstUsed In
stateCSRF attacksOAuth 2.0, OIDC
nonceID token replayOIDC only
PKCEAuthorization code interceptionOAuth 2.0, OIDC

For a deep dive into these three mechanisms and how they differ, see State vs Nonce vs PKCE.

Token Storage

How you store tokens is as important as how you obtain them:

  • Server-side apps: Store tokens in an encrypted server-side session. Never expose them to the browser.
  • SPAs (PKCE direct): Store access tokens in memory (JavaScript variable). Use refresh token rotation with strict lifetimes. Never use localStorage.
  • SPAs (BFF pattern): Tokens stay on the server. The browser only receives an HttpOnly, Secure, SameSite session cookie.
  • Mobile apps: Use platform-specific secure storage (Keychain on iOS, EncryptedSharedPreferences on Android).

Additional Recommendations

  • Always use TLS. OAuth tokens are bearer tokens – anyone who intercepts them gains access.
  • Validate redirect URIs exactly. Register the full URI including path; reject any deviations.
  • Implement token revocation. Call the revocation endpoint on logout (RFC 7009).
  • Use short-lived access tokens. 5-15 minutes is a good range for most applications.
  • Monitor for anomalies. Log token usage patterns and alert on unusual activity (geographic anomalies, excessive token refreshes).

For a broader overview of production-grade patterns, see OAuth 2.0 Best Practices and OAuth 2.0 Deep Dive.

Tools and Libraries

Interactive Developer Tools

Building and debugging OAuth flows is easier with the right tools. IAMDevBox provides several interactive utilities:

  • JWT Decode Tool – Paste any JWT to decode its header, payload, and verify its structure. Essential for debugging token issues.
  • JWT Builder Tool – Construct JWTs with custom claims for testing your resource server validation logic.
  • PKCE Generator Tool – Generate code_verifier and code_challenge pairs for testing PKCE flows.
  • OIDC Discovery Checker – Inspect any OpenID Connect provider’s discovery document and JWKS endpoint.

When implementing OAuth, always use a well-maintained library rather than building from scratch:

JavaScript / Node.js:

  • openid-client – Full-featured OIDC Relying Party library
  • jose – JWT/JWS/JWE/JWK library with no dependencies
  • passport with passport-openidconnect – Express middleware

Java / Spring:

  • Spring Security OAuth 2.0 Client – Built-in support in Spring Boot
  • Nimbus JOSE + JWT – Low-level JWT library
  • Keycloak Java Adapter – For Keycloak deployments

Python:

  • authlib – Comprehensive OAuth/OIDC library
  • PyJWT – JWT encoding and decoding
  • oauthlib – Generic OAuth library used by requests-oauthlib

Go:

  • golang.org/x/oauth2 – Standard OAuth 2.0 package
  • coreos/go-oidc – OIDC client library
  • lestrrat-go/jwx – JWT/JWK library

Authorization Server Products

If you need to run your own authorization server, here are the most common options in the IAM ecosystem:

  • Keycloak – Open-source, feature-rich, supports OIDC, SAML, and OAuth 2.0 out of the box
  • ForgeRock Access Management – Enterprise-grade IAM with advanced policy management
  • PingFederate – Ping Identity’s federation server with broad protocol support
  • Auth0 – Developer-friendly identity platform (now part of Okta)
  • Okta – Workforce and customer identity with extensive API support

Choosing the Right Flow: Decision Tree

Not sure which OAuth flow fits your application? Use this decision tree:

IsthNYeOErSea(uCssleIeirsrevneyitroYNn-uEOvCtrSorole-avdspeeepdnrI?tvaAsieuarStyl,PhNSoYNsAoeiuEObremrSFaoidpltrzlaocahepwhmtipoiguEjbohsaAvoineuablsstlseCecehu,ocaroaadusvrtmpereeieipi?rzc?+t-ayryrtooP?eiusKnoreCdnrEPeavKrCriCeoccBEddheFeisFDwt)ie+ePrbcaeCttcaluttpirepeer?nntSecret+PKCE

For context on how this maps to specific grant types and their trade-offs, see Understanding Authorization Code Flow and Client Credentials Flow.

Putting It All Together

OAuth 2.0 is not a single specification you implement once and forget. It is an ecosystem of interrelated specifications, extensions, and best practices that evolve over time. Here is how the pieces connect:

  1. OAuth 2.0 provides the core authorization framework and grant types.
  2. OpenID Connect adds user authentication on top of OAuth 2.0.
  3. PKCE secures the authorization code flow against interception attacks.
  4. JWTs provide a self-contained, verifiable token format.
  5. Refresh tokens enable long-lived sessions without compromising security.
  6. OAuth 2.1 codifies all of the above into a single, streamlined specification.

The key to a secure implementation is understanding which pieces apply to your architecture, using well-tested libraries, and following the principle of least privilege at every layer.

Further Reading

Explore these articles for deeper coverage of specific topics: