Back to Blog
Guide12 min read2026-07-09

OAuth 2.0 Explained for Developers

OAuth 2.0 lets apps access resources on a user's behalf without handling their password. This guide demystifies the roles, the Authorization Code flow with PKCE, scopes, and common pitfalls.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

# OAuth 2.0 Explained for Developers

Every time you click "Sign in with Google" or let an app access your GitHub repos, you're using OAuth 2.0. It's the industry-standard protocol for delegated authorization — letting one application access resources on a user's behalf without ever seeing that user's password. It's also genuinely confusing the first time, partly because it's solving a subtler problem than people expect.

The problem OAuth solves

Suppose a photo-printing app wants to access your Google Photos. The naive approach: give the app your Google password. Terrible idea — the app now has *full* access to your entire account forever, and you can't revoke just that app.

OAuth solves this: the app gets a *scoped, revocable token* that grants access to *only* your photos, *only* the permissions you approved, *without* ever seeing your password. That's delegated authorization — and it's about *authorization* (what you can access), not strictly *authentication* (who you are). That distinction matters and we'll return to it.

The four roles

OAuth defines four parties:

RoleWho it isExample
Resource OwnerThe user who owns the dataYou
ClientThe app requesting accessThe photo-printing app
Authorization ServerIssues tokens after the user consentsGoogle's OAuth server
Resource ServerHolds the protected dataGoogle Photos API

The whole dance coordinates these four so the Client gets a token from the Authorization Server to call the Resource Server — without the user's credentials ever touching the Client.

The Authorization Code flow (with PKCE)

This is the main flow you should use for web and mobile apps. Here's the sequence:

1. Client redirects user to the Authorization Server
      (with client_id, redirect_uri, scope, state, code_challenge)
2. User authenticates and consents ("Allow this app to view your photos?")
3. Auth Server redirects back to redirect_uri with a short-lived
      authorization code
4. Client exchanges that code (+ code_verifier) at the token endpoint
      → receives an access token (and maybe a refresh token)
5. Client calls the Resource Server with the access token

Why the two-step (code, then token exchange)? The authorization code travels through the user's browser (visible in the redirect URL), but it's useless on its own — exchanging it for a token happens in a separate, more secure back-channel request. The token itself never appears in a browser URL.

PKCE (Proof Key for Code Exchange, RFC 7636) hardens this further. The client generates a random code_verifier, sends its hash (code_challenge) in step 1, and the actual verifier in step 4. This proves the same client that started the flow is finishing it — defeating code-interception attacks. PKCE is now recommended for *all* clients, not just mobile.

Scopes: least privilege for access

Scopes define *what* the token can do:

scope=photos.read
scope=repo:status read:user

The app requests only the scopes it needs, the user sees them on the consent screen, and the token is limited to them. Request the minimum — over-asking spooks users and increases blast radius if the token leaks.

Access tokens and refresh tokens

Like JWT auth, OAuth typically issues two tokens:

  • Access token — short-lived, sent with each API call (often Authorization: Bearer ...). May be a JWT or an opaque string.
  • Refresh token — long-lived, used only to get new access tokens without re-prompting the user. Stored securely; revocable.

When the access token expires, the client uses the refresh token to obtain a new one silently.

Which grant type to use

OAuth has several "grant types" (flows). Modern guidance has narrowed the recommendations considerably:

GrantUse forStatus
Authorization Code + PKCEWeb, SPA, mobile apps✅ Recommended
Client CredentialsMachine-to-machine (no user)✅ Recommended
Device CodeDevices with no browser (TVs, CLIs)✅ For its niche
Implicit(legacy SPAs)❌ Deprecated — use Code+PKCE
Resource Owner Password(app collects password directly)❌ Avoid

The [OAuth 2.0 Security Best Current Practice](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) deprecates the Implicit and Password grants. If a tutorial tells you to use Implicit flow, it's out of date.

OAuth is not authentication (use OIDC for that)

This is the most important conceptual point. OAuth 2.0 is about authorization — granting access to resources. It does *not*, by itself, reliably tell the client *who the user is*. Using a raw OAuth access token as proof of identity is a known anti-pattern (the "confused deputy" / token-substitution problems).

For *authentication* — "Sign in with X" — you want OpenID Connect (OIDC), a thin identity layer built on top of OAuth 2.0. OIDC adds an ID token (a JWT with verified identity claims like sub, email, name) and a standardized userinfo endpoint. So:

  • OAuth 2.0 → "this app may access your photos."
  • OIDC → "this is who the user is."

When you implement "Sign in with Google," you're really using OIDC.

Common pitfalls

  • Skipping the state parameter. It's your CSRF protection for the redirect — generate it, store it, verify it on return. Omitting it opens CSRF on the callback.
  • Loose redirect_uri matching. Register exact redirect URIs; open redirects let attackers steal codes.
  • Treating access tokens as identity. Use OIDC ID tokens for identity, not access tokens.
  • Not using PKCE. It's cheap and closes real attacks; use it everywhere.
  • Storing tokens insecurely (e.g. SPA tokens in localStorage exposed to XSS).
  • Requesting excessive scopes. Least privilege.

A practical example

For a typical web app adding "Sign in with GitHub," you'd: register an OAuth app to get a client_id/client_secret and set the redirect URI; redirect users to GitHub's authorize endpoint with your scopes, a random state, and a PKCE challenge; handle the callback, verify state, exchange the code for tokens; then call the GitHub API with the access token. The client_secret lives server-side as an environment variable — never in client code.

OAuth and PandaStack

PandaStack supports SSO for accessing the platform, and it gives your *own* apps the building blocks to implement OAuth/OIDC correctly: environment variables for your client_id/client_secret and signing keys (kept out of source control), automatic SSL so authorization codes and tokens are never sent in plaintext, managed Redis (via KubeBlocks) for storing state/PKCE values or refresh tokens, and edge functions if you want to handle OAuth callbacks close to the user. You write the flow; the platform keeps the secrets and transport safe.

References

  • [RFC 6749 — OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749)
  • [RFC 7636 — PKCE](https://www.rfc-editor.org/rfc/rfc7636)
  • [OAuth 2.0 Security Best Current Practice (IETF draft)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics)
  • [OpenID Connect](https://openid.net/developers/how-connect-works/)
  • [oauth.net — overview](https://oauth.net/2/)

Implementing OAuth in your app? Keep your client secrets in env vars and ship over automatic SSL with PandaStack's free tier. [Start at dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also