How OAuth 2.0 Works: A Developer’s Visual Guide
Last updated: March 2026
OAuth 2.0 is the authorization framework that underpins nearly every modern authentication flow. It enables applications to request scoped access to resources on behalf of a user — or on their own behalf — without ever handling user credentials directly. Despite its ubiquity, OAuth 2.0 remains one of the most misunderstood protocols in web development.
This guide walks through each major OAuth 2.0 grant type with step-by-step flow descriptions, security considerations, and working curl examples against Keycloak. If you are building a new application or evaluating how to secure an existing one, this is the reference you need.
The Core Concepts
Before diving into grant types, let’s establish the four roles that OAuth 2.0 defines:
- Resource Owner: The user who owns the data and grants access to it.
- Client: The application requesting access (your web app, mobile app, or CLI tool).
- Authorization Server: The server that authenticates the resource owner and issues tokens. In our examples, this is Keycloak.
- Resource Server: The API that holds the protected resources and accepts access tokens.
OAuth 2.0 separates authorization (what you are allowed to do) from authentication (who you are). OpenID Connect (OIDC) adds an identity layer on top of OAuth 2.0 by introducing the ID token. Keycloak implements both OAuth 2.0 and OIDC, making it a complete identity solution. For a deeper look at what these tokens contain, try the JWT Token Analyzer.
Grant Type 1: Authorization Code with PKCE
This is the recommended grant type for most applications — web apps, single-page applications (SPAs), and mobile apps. PKCE (Proof Key for Code Exchange, pronounced “pixy”) was originally designed for public clients but is now recommended for all clients per the OAuth 2.1 draft specification.
The Flow
Step 1: Generate PKCE values. The client generates a random code_verifier (43-128 characters) and derives a code_challenge from it using SHA-256.
# Generate code_verifier (random 43-char string)
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-43)
# Generate code_challenge (SHA-256 hash, base64url-encoded)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=')
Step 2: Redirect the user to the authorization endpoint. The client opens the browser to Keycloak’s authorization endpoint with the code challenge.
GET https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth
?response_type=code
&client_id=my-app
&redirect_uri=http://localhost:3000/callback
&scope=openid profile email
&code_challenge_method=S256
&code_challenge={CODE_CHALLENGE}
&state={RANDOM_STATE}
The state parameter is a random value the client generates and stores locally. It prevents CSRF attacks by ensuring the callback originated from a request the client initiated.
Step 3: The user authenticates. Keycloak presents its login page. The user enters credentials, completes multi-factor authentication if configured, and consents to the requested scopes.
Step 4: Keycloak redirects back with an authorization code.
HTTP/1.1 302 Found
Location: http://localhost:3000/callback?code=abc123&state={RANDOM_STATE}
The client verifies that the state matches what it originally sent.
Step 5: Exchange the authorization code for tokens. The client sends the authorization code and the original code_verifier to the token endpoint.
curl -X POST https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
-d "grant_type=authorization_code"
-d "client_id=my-app"
-d "redirect_uri=http://localhost:3000/callback"
-d "code=abc123"
-d "code_verifier=$CODE_VERIFIER"
Step 6: Receive tokens. Keycloak validates the code verifier against the original code challenge. If they match, it returns tokens:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "eyJhbGciOi...",
"id_token": "eyJhbGciOi...",
"scope": "openid profile email"
}
Why PKCE Matters
Without PKCE, an attacker who intercepts the authorization code (via a malicious browser extension, a compromised redirect, or a custom URL scheme collision on mobile) can exchange it for tokens. PKCE ensures only the client that initiated the flow can complete it, because only that client knows the code_verifier.
When to Use It
- Web applications (server-rendered or SPA)
- Mobile and desktop applications
- Any scenario where a user is present at the keyboard
For SPAs specifically, consider pairing this with the Backend-for-Frontend pattern to keep tokens off the client entirely.
Grant Type 2: Client Credentials
The client credentials grant is for machine-to-machine (M2M) communication where no user is involved. A backend service authenticates using its own credentials to access another service’s API.
The Flow
Step 1: The client authenticates directly with the token endpoint.
curl -X POST https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
-d "grant_type=client_credentials"
-d "client_id=billing-service"
-d "client_secret=my-service-secret"
-d "scope=api:billing"
Step 2: Receive an access token. There is no authorization code, no redirect, and no user interaction.
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 300,
"scope": "api:billing"
}
There is no refresh token in this flow. When the access token expires, the client simply requests a new one.
Security Considerations
- Protect the client secret. It is the equivalent of a service account password. Store it in a secrets manager, not in source code.
- Use short-lived tokens. Set
access_token_lifespanto 5 minutes or less in Keycloak’s realm settings. - Scope narrowly. Create dedicated scopes for each service interaction. Do not grant broad permissions.
- Rotate secrets. Keycloak supports having multiple active client secrets, allowing zero-downtime rotation.
When to Use It
- Microservice-to-microservice API calls
- Background jobs and cron tasks
- CI/CD pipelines accessing protected APIs
For a deeper dive into M2M patterns, see Keycloak Machine-to-Machine Authentication.
Grant Type 3: Device Authorization (Device Flow)
The device authorization grant (RFC 8628) is designed for devices that lack a browser or have limited input capability — smart TVs, IoT devices, CLI tools, and printers.
The Flow
Step 1: The device requests a device code.
curl -X POST https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth/device
-d "client_id=smart-tv-app"
-d "scope=openid profile"
Step 2: Keycloak returns a device code and a user code.
{
"device_code": "GmRhm...k8Bz",
"user_code": "WDJB-MJHT",
"verification_uri": "https://keycloak.example.com/realms/myrealm/device",
"verification_uri_complete": "https://keycloak.example.com/realms/myrealm/device?user_code=WDJB-MJHT",
"expires_in": 600,
"interval": 5
}
Step 3: The device displays the user code. The device shows a message like: “Go to https://keycloak.example.com/realms/myrealm/device and enter code WDJB-MJHT.”
Step 4: The user authenticates on a separate device. The user opens the verification URI on their phone or laptop, enters the user code, logs in, and approves the request.
Step 5: The device polls the token endpoint.
curl -X POST https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code"
-d "client_id=smart-tv-app"
-d "device_code=GmRhm...k8Bz"
While the user has not yet authorized, Keycloak returns:
{
"error": "authorization_pending"
}
The device waits the interval seconds (5 in this case) before polling again. Once the user completes authorization, the token endpoint returns the full token set.
Security Considerations
- Respect the polling interval. Keycloak will return
slow_downif you poll too frequently. - Short device code lifetime. The
expires_inon the device code should be kept short (10 minutes or less). - Rate limit device code requests. Prevent abuse by limiting how many device codes a single client can request.
When to Use It
- Smart TV applications
- IoT devices and embedded systems
- CLI tools where opening a browser is awkward
Deprecated and Removed Grant Types
Resource Owner Password Credentials (ROPC)
This grant type allowed clients to collect username and password directly and exchange them for tokens. It was deprecated in OAuth 2.1 and removed from Keycloak 26. Do not use it for new implementations. If you have existing code using ROPC, migrate to the authorization code flow with PKCE.
Implicit Flow
The implicit flow returned tokens directly in the URL fragment. It was deprecated because tokens in URL fragments are exposed in browser history, referrer headers, and logs. OAuth 2.1 removes it entirely. Use authorization code with PKCE instead.
OpenID Connect: The Identity Layer
OAuth 2.0 handles authorization, but it does not tell you who the user is. OpenID Connect adds three things:
- ID Token: A JWT containing claims about the user (sub, email, name, etc.). Decode one with the JWT Token Analyzer to see what’s inside.
- UserInfo Endpoint: An API endpoint that returns user profile information.
- Discovery Document: A
.well-known/openid-configurationendpoint that describes the provider’s capabilities.
# Fetch the OIDC discovery document
curl https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration | jq .
This returns all supported endpoints, grant types, scopes, and signing algorithms. Client libraries use this to auto-configure themselves.
Keycloak Client Configuration
To use any of these flows, you need to configure a client in Keycloak. Here is a summary of how to set up each type:
Authorization Code with PKCE (for a web app)
In the Keycloak admin console:
- Create a new client with Client type set to
OpenID Connect. - Set Client authentication to
Offfor SPAs,Onfor server-side apps. - Enable Standard flow (this is the authorization code flow).
- Add your redirect URI (e.g.,
http://localhost:3000/callback). - Under Advanced > Proof Key for Code Exchange, set to
S256.
Client Credentials (for a service)
- Create a new client with Client type set to
OpenID Connect. - Set Client authentication to
On. - Disable Standard flow and Direct access grants.
- Enable Service accounts roles.
- Assign the appropriate roles under Service account roles.
Device Flow
- Create a new client with Client type set to
OpenID Connect. - Set Client authentication to
Off. - Enable OAuth 2.0 Device Authorization Grant.
For quick local setup, use the Keycloak Docker Compose Generator to spin up a Keycloak instance with PostgreSQL and proper volume mounts in seconds.
Token Validation
Once your application receives an access token, it needs to validate it before trusting its claims. There are two approaches:
Local Validation (Recommended)
Validate the token’s JWT signature using Keycloak’s public keys:
// Node.js example using jose
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs')
);
async function validateToken(token) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://keycloak.example.com/realms/myrealm',
audience: 'my-app',
});
return payload;
}
Introspection (For opaque tokens)
If you are using opaque tokens or need real-time revocation checks:
curl -X POST https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token/introspect
-d "token=ACCESS_TOKEN_HERE"
-d "client_id=my-resource-server"
-d "client_secret=resource-server-secret"
For a complete guide to token validation patterns, see Keycloak Token Validation for APIs.
Scopes and Claims
Scopes control what information is included in the tokens. Keycloak provides several built-in scopes:
| Scope | Claims Included |
|---|---|
openid |
sub (required for OIDC) |
profile |
name, family_name, given_name, preferred_username |
email |
email, email_verified |
roles |
realm_access.roles, resource_access.{client}.roles |
You can create custom scopes in Keycloak to include additional claims. This is useful when your APIs need domain-specific information (e.g., a tenant_id or subscription_tier). See Using Custom User Attributes in Keycloak OIDC Tokens for implementation details.
Choosing the Right Grant Type
Here is a decision framework:
Is a user present?
- No — Use Client Credentials. This is machine-to-machine communication.
- Yes — Continue below.
Does the device have a browser?
- No — Use Device Authorization Flow.
- Yes — Continue below.
Is the client a server-side application?
- Yes — Use Authorization Code with PKCE (confidential client with client secret).
- No — Use Authorization Code with PKCE (public client, no client secret).
In both cases for user-facing apps, you use the authorization code flow. The difference is whether the client also authenticates with a client secret during the token exchange.
Security Best Practices
- Always use PKCE, even for confidential clients. It adds defense in depth.
- Use short-lived access tokens (5-15 minutes). Rely on refresh tokens for session continuation.
- Store refresh tokens securely. On the server, in encrypted cookies, or in a secure keychain on mobile.
- Validate the
stateparameter to prevent CSRF attacks. - Validate the
noncein ID tokens to prevent replay attacks. - Use
response_mode=fragmentfor SPAs to prevent authorization codes from appearing in server logs. - Restrict redirect URIs. Never use wildcard redirect URIs. Be as specific as possible.
- Monitor token usage. Keycloak’s audit logs capture token issuance, refresh, and revocation events.
For a look at what OAuth 2.1 changes, see Upcoming Changes in OAuth 2.1: What You Need to Know.
Testing OAuth Flows Locally
The fastest way to experiment with these flows is to run Keycloak locally:
docker run -p 8080:8080
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin
quay.io/keycloak/keycloak:26.1.0 start-dev
Then create a realm, a client, and a user through the admin console at http://localhost:8080. Alternatively, use the Keycloak Config Generator to produce a ready-to-import realm configuration.
For single sign-on across multiple applications, you can configure multiple clients in the same realm. Each client gets its own redirect URIs and scopes, but users authenticate once and get tokens for all of them.
Next Steps
OAuth 2.0 is the foundation, but real-world implementations require decisions about session management, token lifetimes, multi-factor authentication, and identity provider federation. Here are resources to continue:
- SSO Implementation Guide: SAML and OIDC with Keycloak for protocol-level SSO setup
- Keycloak Token Lifecycle Management for refresh and revocation strategies
- Skycloak Documentation for managed Keycloak configuration guides
- Keycloak Server Administration Guide for comprehensive reference
Ready to implement OAuth 2.0 without managing infrastructure? Skycloak provides fully managed Keycloak with built-in high availability, automated backups, and SOC 2 compliance. Check our pricing plans to find the right fit for your project.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.