OAuth 2.0, Scopes and Authorization

Advanced
15 min

OAuth 2.0, Scopes and Authorization

How does a user let one app access their data in another app without sharing a password? That is what OAuth 2.0 solves: it is not a token format but a protocol for obtaining a bearer token with the user's consent. In this lesson you will learn the OAuth roles and flows that matter today, how a resource server validates bearer tokens, and how scopes, roles and ownership checks combine into authorization.

The Authorization Code Flow with PKCE

OAuth defines four roles: the resource owner (user), the client (app wanting access), the authorization server (issues tokens) and the resource server (your API). Web and mobile apps should use Authorization Code with PKCE, safe even for public clients that cannot keep a secret:

  1. The client generates a random code_verifier and derives code_challenge = base64url(sha256(verifier)).
  2. It redirects the user to /authorize with the challenge, the requested scopes and a random state.
  3. The user logs in and consents; the browser returns to redirect_uri with a short-lived code.
  4. The client exchanges code plus verifier for tokens.
bash
# Step 4: back-channel exchange curl -X POST https://auth.example.com/token \ -d grant_type=authorization_code -d code=SplxlOBeZQQYbYS6WxSbIA \ -d redirect_uri=https://app.example.com/callback -d client_id=web-app \ -d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
json
{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "8xLOxBtZp8", "scope": "orders:read orders:write" }

A stolen code is useless without the verifier, and state protects the callback against CSRF.

Client Credentials and Refresh Tokens

When no user is involved, a backend service uses the Client Credentials grant: POST /token with grant_type=client_credentials, client_id and client_secret, receiving a token for its own identity. Access tokens are short-lived; when one expires, the client sends grant_type=refresh_token for a new one without involving the user. Refresh tokens are long-lived secrets; rotate them on use.

Validating Tokens on the Resource Server

Your API never sees passwords. It receives Authorization: Bearer <token> and validates it locally when the token is a JWT (verify the signature with the authorization server's published JWKS keys, then check exp, iss and aud) or through the introspection endpoint for opaque tokens. A missing or invalid token gets 401; a valid token lacking permission gets 403.

Scopes, Roles and Ownership

Three questions hide inside "is this allowed?":

  • Scope: what did the user permit this client application to do? Carried in the token, checked per endpoint.
  • Role: what may this user do in your system? Checked for functions such as refunds or admin pages.
  • Ownership: may this user touch this specific object? Checked against the record itself.
javascript
// req.auth was populated by JWT verification: { sub, scope: "orders:read", roles: [] } function requireScope(...needed) { return (req, res, next) => { const granted = (req.auth?.scope ?? "").split(" "); if (!needed.every((s) => granted.includes(s))) { return res.status(403).json({ error: "insufficient_scope", required: needed }); } next(); }; } app.get("/orders/:id", requireScope("orders:read"), async (req, res) => { const order = await db.orders.findById(req.params.id); if (!order) return res.status(404).json({ error: "Order not found" }); if (order.customerId !== req.auth.sub && !req.auth.roles?.includes("admin")) { return res.status(404).json({ error: "Order not found" }); // do not reveal existence } res.json(order); });

Skipping the ownership check is the top item in the OWASP API Security Top 10, Broken Object Level Authorization: an attacker just increments the id. Skipping role checks on admin endpoints is its sibling, Broken Function Level Authorization.

Common Mistakes

  • Treating a scope as a role. orders:write means the app may write orders for this user, not anyone's orders.
  • Putting access tokens in URLs or localStorage instead of the Authorization header.
Quick Quiz
Question 1 of 2

Why does PKCE protect against a stolen authorization code?

Key Takeaways

  • OAuth 2.0 is a protocol for delegated access; the result is a bearer token, usually a JWT.
  • Use Authorization Code with PKCE for apps with users and Client Credentials for service-to-service calls.
  • Resource servers validate tokens via JWKS or introspection and answer 401 or 403 precisely.
  • Scopes limit the client app, roles limit the user, and ownership checks protect individual objects.

Next lesson: API Versioning Strategies — evolve an API without breaking the clients that already depend on it.

OAuth 2.0, Scopes and Authorization - REST APIs | CodeYourCraft | CodeYourCraft