Skip to content

OAuth2 Scopes — Granular Permission Control for Token-Based Authorization

DodaTech Updated 2026-06-28 3 min read

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 Google
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

  1. What problem do scopes solve?
  2. What is the recommended scope naming format?
  3. How does the client request specific scopes?
  4. Where are scopes enforced?
  5. What happens if requested scope exceeds granted scope?

Answers:

  1. Scopes enable granular permissions so a token grants only the access the application needs.
  2. resource:action (e.g., reports:read) or URL-based for public APIs.
  3. By including the scope parameter in the authorization request with space-separated values.
  4. Both authorization server (records granted scopes) and resource server (validates scopes on each request).
  5. 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

What is the difference between a scope and a role?

A scope is a permission (can read reports). A role is a collection of scopes (admin = read + write + delete).

How are scopes delivered in a token?

As a space-separated string in the scope claim: 'scope': 'reports:read users:read'

Can scopes be added to a token after issuance?

Generally no. The client must re-authorize with additional scopes.

What is the openid scope?

Required for OpenID Connect. Signals that the client wants an ID token.

How many scopes should a token have?

As few as possible. A reporting app might have 2-3 scopes.

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