OAuth2 Scopes — Granular Permission Control for Token-Based Authorization
In this tutorial, you will learn about OAuth2 Scopes. We cover key concepts, practical examples, and best practices to help you master this topic.
OAuth2 scopes define the specific permissions a client requests, enabling granular access control where tokens are limited to precisely the operations the client needs.
What You'll Learn
How scopes work, scope naming conventions, how clients request scopes, how resource servers enforce them, and how consent screens present them to users.
Why It Matters
Without scopes, a token either grants full access or no access. Scopes implement the principle of Least Privilege. A read-only integration cannot delete data even if the token is compromised.
Real-World Use
Google Calendar scopes: https://www.googleapis.com/auth/calendar.readonly vs calendar. GitHub scopes: repo (full) vs repo:read. Slack scopes: chat:write, users:read.
flowchart LR
A["Client"] -->|"Request scopes"| B["Consent Screen"]
B -->|"User approves scope set"| C["Auth Server"]
C -->|"Token with scopes"| A
A -->|"API call"| D["Resource Server"]
D -->|"Check scope"| E["Enough permission?"]
E -->|"Yes"| F["200 OK"]
E -->|"No"| G["403 Forbidden"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style D fill:#dcfce7,stroke:#16a34a
style G fill:#fecaca,stroke:#dc2626
Scope Naming Conventions
| Format | Example | Provider |
|---|---|---|
resource:action |
reports:read |
Custom |
resource.action |
contacts.read |
|
| URL-based | https://api.example.com/auth/docs.readonly |
Google/others |
Code Example: Enforcing Scopes on Resource Server
from flask import Flask, request, jsonify, g
from functools import wraps
import jwt
app = Flask(__name__)
SECRET = "your-secret"
def require_scope(required_scope):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", "")
token = auth[7:] if auth.startswith("Bearer ") else None
if not token:
return jsonify({"error": "Missing token"}), 401
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
token_scopes = payload.get("scope", "").split()
if required_scope not in token_scopes:
return jsonify({
"error": "insufficient_scope",
"required": required_scope,
"granted": token_scopes
}), 403
g.user = payload
except jwt.InvalidTokenError:
return jsonify({"error": "Invalid token"}), 401
return f(*args, **kwargs)
return wrapper
return decorator
@app.route("/api/reports")
@require_scope("reports:read")
def get_reports():
return jsonify({"reports": ["q1", "q2"]})
@app.route("/api/reports", methods=["POST"])
@require_scope("reports:write")
def create_report():
return jsonify({"message": "Created"})
Common Mistakes
1. Requesting All Scopes by Default
Asking for full access when you only need read-only violates least privilege.
2. Not Checking Scopes on Resource Server
The auth server records scopes, but the resource server must enforce them.
3. Ambiguous Scope Names
read, write, admin are too vague. reports:read is clear.
4. Ignoring Unknown Scopes
If a client requests an unknown scope, either reject or silently ignore it.
5. Not Documenting Scopes
Developers need to know what each scope grants. Document every scope.
Practice Questions
- What problem do scopes solve?
- What is the recommended scope naming format?
- How does the client request specific scopes?
- Where are scopes enforced?
- What happens if requested scope exceeds granted scope?
Answers:
- Scopes enable granular permissions so a token grants only the access the application needs.
resource:action(e.g.,reports:read) or URL-based for public APIs.- By including the
scopeparameter in the authorization request with space-separated values. - Both authorization server (records granted scopes) and resource server (validates scopes on each request).
- The granted scopes in the token may be a subset. The client should check granted scopes.
Challenge: Design a scope system for a document management API with read, create, update, delete, and admin scopes. Show how a client requests minimal scopes.
FAQ
Mini Project
Create a Flask resource server with scope-based enforcement, define scopes for a task management API (tasks:read, tasks:write, tasks:delete), and test with different scope combinations.
What's Next
Now learn about OAuth2 Access Tokens — the core credential in every OAuth2 flow.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro