Backend

OAuth 2.0 Explained for Backend Developers

A backend-focused OAuth 2.0 guide covering roles, authorization code flow, PKCE, scopes, tokens, client credentials, and common mistakes.

Quick answer

OAuth 2.0 is an authorization framework. It lets one application access protected resources on behalf of a user or another system without sharing the user’s password with that application.

For backend developers, the most important OAuth 2.0 ideas are roles, authorization flows, scopes, redirect URI validation, token handling, and the difference between authorization and authentication.

OAuth is not the same as login

OAuth 2.0 answers this question:

Can this client access this resource with this permission?

It does not, by itself, define a complete user identity protocol. OpenID Connect builds on OAuth 2.0 and adds identity concepts such as ID tokens and standardized user profile claims.

In practice, many products say “OAuth login” when they mean OpenID Connect or a provider-specific identity flow. The distinction matters because backend systems should not treat an access token as a full identity proof unless the architecture explicitly supports that.

The main OAuth roles

OAuth uses a few core roles:

RoleMeaning
Resource ownerThe user or entity that owns the protected resource.
ClientThe application requesting access.
Authorization serverThe system that authenticates the user and issues tokens.
Resource serverThe API that accepts access tokens and protects resources.

In a typical web app, your backend may be the client, the resource server, or both. For example, your app may call GitHub as a client, while also exposing its own API as a resource server.

Authorization code flow

The authorization code flow is the common flow for server-rendered web apps and many modern applications.

At a high level:

  1. The client redirects the user to the authorization server.
  2. The user authenticates and grants permission.
  3. The authorization server redirects back with an authorization code.
  4. The backend exchanges the code for tokens.
  5. The backend uses the access token to call APIs.

The authorization code is short-lived and should be exchanged from a trusted backend when possible.

GET /oauth/authorize?response_type=code&client_id=client_123&redirect_uri=https://app.example.com/callback&scope=profile

After the callback, the backend exchanges the code:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=abc123
&redirect_uri=https://app.example.com/callback
&client_id=client_123

PKCE

PKCE, pronounced “pixy”, protects the authorization code flow by binding the token exchange to a secret verifier created by the client.

The client starts with a code_verifier, derives a code_challenge, and sends the challenge during authorization. Later, the client sends the verifier during token exchange. The authorization server checks that the verifier matches the challenge.

PKCE is important for public clients such as browser and mobile apps because those clients cannot safely store a client secret. It is also useful as a defense-in-depth measure for other clients.

Client credentials flow

The client credentials flow is for machine-to-machine access, not user delegation.

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=service_a
&client_secret=secret
&scope=reports:read

Use this when a backend service needs to call another backend service as itself. Do not use it when the operation needs a user’s consent or user-specific permissions.

Refresh token flow

Refresh tokens let a client obtain a new access token without forcing the user to sign in again.

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=rft_abc123

Refresh tokens are high-value credentials. Store them carefully, rotate them when possible, and revoke them when a user logs out, changes password, or loses device trust.

Read Access Token vs Refresh Token for the token lifecycle details.

Scopes

Scopes describe what access the client is requesting or has received. Examples:

profile email orders:read orders:write

Scopes should be meaningful and limited. Avoid issuing broad scopes such as admin unless the client truly needs them. Backend APIs should check scopes or permissions before performing protected actions.

Scopes are not a replacement for authorization logic. A scope may say the token can call order APIs, but the backend still needs to check tenant, account, ownership, role, or policy rules.

State and redirect URI validation

The state parameter helps connect the OAuth callback to the original request and reduces cross-site request forgery risk in the OAuth flow.

The redirect URI is also security-critical. Authorization servers should require exact redirect URI matching or a tightly controlled allowlist. Loose wildcard redirects can leak authorization codes or tokens to attacker-controlled URLs.

Backend developers should treat redirect handling as part of the security boundary, not as a routing detail.

Access token validation

When your API acts as a resource server, it must validate incoming access tokens before trusting them.

For JWT access tokens, typical checks include:

  • Signature.
  • Issuer.
  • Audience.
  • Expiration.
  • Not-before time when present.
  • Required scopes or permissions.
  • Tenant or account context when relevant.

For opaque access tokens, the API usually calls an introspection endpoint or shared token store.

Never trust a token only because it is shaped like a JWT. The API must verify it according to the expected issuer and audience.

OAuth vs OpenID Connect

Use OAuth 2.0 for authorization: granting access to resources.

Use OpenID Connect when the client needs standardized user identity information. OpenID Connect adds ID tokens and user identity claims on top of OAuth 2.0.

A common mistake is using an OAuth access token as if it were an ID token. Access tokens are intended for APIs. ID tokens are intended for the client to understand the authenticated user.

Common backend mistakes

The first mistake is using an outdated or unsafe flow because it appears simpler. Prefer authorization code with PKCE for interactive user flows.

The second mistake is accepting any JWT from any issuer. Always validate issuer, audience, signature, expiration, and expected claims.

The third mistake is storing client secrets in frontend code. Browser and mobile apps are public clients; secrets in those apps are not secret.

The fourth mistake is using scopes as the only authorization layer. Scopes help, but object-level and tenant-level authorization still matter.

The fifth mistake is logging tokens, authorization codes, client secrets, or refresh tokens. Redact them before logs, traces, and analytics events.

Practical backend checklist

Before shipping an OAuth integration:

  • Register exact redirect URIs.
  • Use state for interactive flows.
  • Use PKCE for public clients.
  • Store client secrets only on trusted backends.
  • Validate issuer, audience, signature, and expiration.
  • Keep access tokens short-lived.
  • Rotate refresh tokens when possible.
  • Redact credentials from logs.
  • Document scopes and token lifetimes.
  • Test logout, revocation, expired token, and invalid scope behavior.

Read Access Token vs Refresh Token for token lifecycle tradeoffs and JWT vs Session Authentication for broader authentication architecture. Use the JWT Decoder to inspect test JWT claims locally while remembering that APIs must still verify signatures and claims. For browser-side risks around cookies, tokens, and forms, read CSRF vs XSS. For API error behavior, read REST API Status Codes Explained. You can also browse the Authentication topic cluster for related backend security articles.