How OAuth 2.0 Works: A Developer’s Visual Guide

Guilliano Molaire Guilliano Molaire Updated May 8, 2026 9 min read

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:

  1. Resource Owner: The user who owns the data and grants access to it.
  2. Client: The application requesting access (your web app, mobile app, or CLI tool).
  3. Authorization Server: The server that authenticates the resource owner and issues tokens. In our examples, this is Keycloak.
  4. 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_lifespan to 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_down if you poll too frequently.
  • Short device code lifetime. The expires_in on 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:

  1. 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.
  2. UserInfo Endpoint: An API endpoint that returns user profile information.
  3. Discovery Document: A .well-known/openid-configuration endpoint 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:

  1. Create a new client with Client type set to OpenID Connect.
  2. Set Client authentication to Off for SPAs, On for server-side apps.
  3. Enable Standard flow (this is the authorization code flow).
  4. Add your redirect URI (e.g., http://localhost:3000/callback).
  5. Under Advanced > Proof Key for Code Exchange, set to S256.

Client Credentials (for a service)

  1. Create a new client with Client type set to OpenID Connect.
  2. Set Client authentication to On.
  3. Disable Standard flow and Direct access grants.
  4. Enable Service accounts roles.
  5. Assign the appropriate roles under Service account roles.

Device Flow

  1. Create a new client with Client type set to OpenID Connect.
  2. Set Client authentication to Off.
  3. 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

  1. Always use PKCE, even for confidential clients. It adds defense in depth.
  2. Use short-lived access tokens (5-15 minutes). Rely on refresh tokens for session continuation.
  3. Store refresh tokens securely. On the server, in encrypted cookies, or in a secure keychain on mobile.
  4. Validate the state parameter to prevent CSRF attacks.
  5. Validate the nonce in ID tokens to prevent replay attacks.
  6. Use response_mode=fragment for SPAs to prevent authorization codes from appearing in server logs.
  7. Restrict redirect URIs. Never use wildcard redirect URIs. Be as specific as possible.
  8. 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:


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.

Guilliano Molaire
Written by Guilliano Molaire Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Ready to simplify your authentication?

Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.

© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman