Skip to content

OAuth2 Resource Server — Protecting APIs with Token Validation

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about OAuth2 Resource Server. We cover key concepts, practical examples, and best practices to help you master this topic.

The resource server hosts protected data and validates access tokens on every request, ensuring only authorized clients can access resources.

What You'll Learn

How resource servers validate tokens locally or via introspection, enforce scopes, handle token formats, and respond to authorization failures.

Why It Matters

The resource server is the final barrier — if it accepts an invalid token, all other OAuth2 security is bypassed. Proper token validation here is critical.

Real-World Use

Google Drive API is a resource server. It validates tokens from Google Accounts. GitHub API validates tokens from GitHub OAuth. Each resource server independently validates tokens.

flowchart LR
    A["Client"] -->|"Request + Bearer Token"| B["Resource Server"]
    B -->{"Token Format?"}
    B -->|"JWT"| C["Validate signature\nCheck claims"]
    B -->|"Opaque"| D["Introspection endpoint"]
    C -->|"Valid"| E{"Check scopes"}
    D -->|"Active"| E
    E -->|"Sufficient"| F["200 OK + Data"]
    E -->|"Insufficient"| G["403 Forbidden"]
    C -->|"Invalid"| H["401 Unauthorized"]
    D -->|"Inactive"| H
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style F fill:#dcfce7,stroke:#16a34a
    style G fill:#fecaca,stroke:#dc2626
    style H fill:#fecaca,stroke:#dc2626

Token Validation Strategies

Strategy Description Best For
Local (JWT) Validate signature + claims without network High-performance APIs
Introspection Call auth server to validate token Opaque tokens, immediate revocation
Cached Introspection Cache validation results briefly Balance of speed and revocation

Code Example: Resource Server with Introspection

import requests
from flask import Flask, request, jsonify, g

app = Flask(__name__)
INTROSPECT_URL = "https://auth.example.com/introspect"
CLIENT_ID = "resource-server"
CLIENT_SECRET = "rs-secret"

def validate_token(token):
    """Call introspection endpoint to validate the token."""
    response = requests.post(
        INTROSPECT_URL,
        data={"token": token},
        auth=(CLIENT_ID, CLIENT_SECRET)
    )
    if response.status_code != 200:
        return None
    result = response.json()
    if not result.get("active"):
        return None
    return result

@app.route("/api/data")
def get_data():
    auth = request.headers.get("Authorization", "")
    token = auth[7:] if auth.startswith("Bearer ") else None
    if not token:
        return jsonify({"error": "Missing token"}), 401

    token_info = validate_token(token)
    if not token_info:
        return jsonify({"error": "Invalid or expired token"}), 401

    # Enforce scopes
    scopes = token_info.get("scope", "").split()
    if "data:read" not in scopes:
        return jsonify({
            "error": "insufficient_scope",
            "required": "data:read"
        }), 403

    return jsonify({
        "data": "protected content",
        "user": token_info.get("sub"),
        "client": token_info.get("client_id")
    })

Common Mistakes

1. Not Validating Tokens on Every Request

Some developers cache token validation indefinitely. Always validate. Use short-lived cache only.

2. Trusting the Token Without Checking Scopes

A valid token does not mean authorized. Always check scopes.

3. Not Handling Introspection Failures

If the introspection endpoint is down, the resource server should fail closed (deny access).

4. Using JWT Without Validating All Claims

Validate exp, iss, aud, and scope. A token with all correct claims except the audience is invalid.

5. Leaking Token Data in Logs

Never log full tokens. Log truncated values or jti only.

Practice Questions

  1. What is the difference between a resource server and an authorization server?
  2. How does a resource server validate a JWT access token?
  3. How does it validate an opaque token?
  4. Why must the resource server enforce scopes?
  5. What happens if the introspection endpoint is unavailable?

Answers:

  1. Auth server issues tokens. Resource server validates them and protects data.
  2. It checks the signature using the auth server's public key (from JWKS), validates claims (exp, iss, aud), and enforces scopes.
  3. It calls the introspection endpoint (or caches previous results).
  4. The token is valid, but may have insufficient permissions. The resource server checks scopes to enforce Least Privilege.
  5. Fail closed — deny all requests until introspection is available. This is safer than allowing unvalidated access.

Challenge: Build a resource server that supports both JWT (local validation using JWKS) and opaque (introspection) tokens. The server should detect the token type and validate accordingly.

FAQ

Can the resource server cache token validation?

Yes, but with short TTL (1-5 minutes). Long caching defeats revocation.

Does the resource server need to contact the auth server for every request?

For JWTs: no (local signature validation). For opaque tokens: yes (introspection) unless caching.

Should the resource server have its own client_id?

Yes. For introspection, the resource server authenticates with its own credentials.

How does the resource server get the JWKS?

From the auth server's /.well-known/jwks.json endpoint. Fetch on startup and periodically refresh.

What HTTP status codes should the resource server return?

401 for missing/invalid token, 403 for valid token but insufficient scopes, 200 for success.

Mini Project

Create a Flask resource server that validates access tokens (both JWT and opaque), enforces scopes, and provides different endpoints with different scope requirements.

What's Next

Now learn about OAuth2 Security — CSRF, code injection, and other security considerations.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro