API & Automation·7 min read·

An OAuth 2.0 Flow Survey for API Clients

Compare the authorization code and client credentials OAuth 2.0 grants to choose the right delegation flow for your API clients and integrations.

NB

Netbay Infrastructure Team

Netbay Engineering

On this page

OAuth 2.0 is the protocol you use whenever you do not want to hand credentials to a third-party app. Rather than sharing a password, the resource owner authorizes a scoped delegation. For API clients, two grants dominate: the **authorization code flow** for apps acting on a user's behalf, and **client credentials** for server-to-server calls. This survey walks through when to use each and how they differ under the hood.

The actors in any OAuth exchange

  • **Resource owner** — the user who controls the data.
  • **Client** — the application requesting access.
  • **Authorization server** — issues tokens after verifying identity.
  • **Resource server** — serves the protected API when handed a valid token.

The client never sees the user's password. Instead it receives an authorization grant and exchanges it for an access token, usually scoped and short-lived.

Authorization code flow

This is the gold standard for apps that act on behalf of a user. The flow redirects the user to the authorization server, where they log in and approve a requested scope. The server returns a one-time authorization code to a redirect URI, which the client exchanges for an access token plus a refresh token.

Adding the **PKCE** extension (a proof key for code exchange) protects mobile and single-page clients where the client secret cannot be kept hidden. Even confidential clients should use it — it neutralizes the code-interception risk across every device.

Client app sends code + PKCE Authorization server verifies, returns token 1. code 2. token Resource server data on demand User browser logs in, approves scope

Client credentials flow

This grant has no user at all. The client authenticates directly with its own 'client_id' and 'client_secret', and the authorization server returns an access token scoped to the client itself. It is the natural fit for scheduled jobs, background sync, and internal service-to-service calls where no human is in the loop.

Because there is no user involvement, you cannot rescope per person — you scope per client. Keep the secret server-side only, and lean on the grant's short token lifetimes plus refresh as needed.

Comparing the two grants

  1. Use **authorization code (+ PKCE)** when the API acts on behalf of a user and you must respect per-user scopes.
  2. Use **client credentials** when the API user is the application itself with no impersonation.
  3. Reserve **password grant** for legacy flows only; it re-exposes credentials and should be avoided.
  4. Always send credentials over TLS and never log tokens or secrets.

Whichever grant you pick, scope hygiene decides whether a breach is contained. Ask for the narrowest set of scopes the client actually uses, and reject tokens that carry more access than the resource server is prepared to grant. It is a common failure to request read:everything and then route all traffic through that one token, which turns any single leak into full read access across the platform. Enforce the requested scope on the resource server itself rather than trusting the client to self-limit, since a client that asks for more than it needs is either careless or already compromised.

Also decide where tokens get verified. If every microservice independently validates a shared signing key, you avoid a central lookup on each hop, but you inherit the challenge of synchronizing that key and rotating it safely. If instead only an auth gateway verifies and then forwards a derived identity, you lose some statelessness but gain a single place to enforce revocation. Pick one model deliberately instead of letting it emerge by accident.

A client credentials request, plain text

bash
curl -X POST https://auth.example.com/oauth/token   -H "Content-Type: application/x-www-form-urlencoded"   -d "grant_type=client_credentials"   -d "client_id=your-client-id"   -d "client_secret=your-client-secret"   -d "scope=read:orders write:invoices"

Exchanging an authorization code with PKCE

python
import requests, hashlib, base64, os

verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=")
challenge = base64.urlsafe_b64encode(
    hashlib.sha256(verifier).digest()
).rstrip(b"=")

resp = requests.post("https://auth.example.com/oauth/token", json={
    "grant_type": "authorization_code",
    "code": "AUTH_CODE",
    "redirect_uri": "https://app.example.com/callback",
    "client_id": "your-client-id",
    "code_verifier": verifier.decode(),
})
print(resp.json()["access_token"])

Takeaway

Pick the grant by who the actor is: a user means authorization code with PKCE, a machine means client credentials. Scope tightly, keep secrets server-side, and use TLS everywhere. Netbay VPS deploys are API-driven, so you can wire these very flows against your own infrastructure at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance