OAuth2 Resource Server — Protecting APIs with Token Validation
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
- What is the difference between a resource server and an authorization server?
- How does a resource server validate a JWT access token?
- How does it validate an opaque token?
- Why must the resource server enforce scopes?
- What happens if the introspection endpoint is unavailable?
Answers:
- Auth server issues tokens. Resource server validates them and protects data.
- It checks the signature using the auth server's public key (from JWKS), validates claims (exp, iss, aud), and enforces scopes.
- It calls the introspection endpoint (or caches previous results).
- The token is valid, but may have insufficient permissions. The resource server checks scopes to enforce Least Privilege.
- 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
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