What Are OAuth 2.0 Scopes and How Do They Work?

OAuth 2.0 scopes define what permissions an access token has. Learn how scopes work, how to design them, and how Modern PetstoreAPI implements granular scope-based authorization.

Ashley Innocent

Ashley Innocent

13 March 2026

What Are OAuth 2.0 Scopes and How Do They Work?

TL;DR

OAuth 2.0 scopes are permission strings that define what an access token can do. Use format resource:action like pets:read or orders:write. Request scopes during authorization, validate them on API endpoints. Modern PetstoreAPI implements scopes for read/write access to pets, orders, and user data.

Introduction

A third-party app wants to read your pet store’s inventory. Should it have full access to create orders, delete pets, and manage users? No. It should only read inventory.

OAuth 2.0 scopes solve this. Scopes define what permissions an access token has. The app requests inventory:read scope. Your API validates the token has this scope before returning data.

Modern PetstoreAPI implements granular scopes for all resources: pets, orders, inventory, and users.

If you’re testing OAuth APIs, Apidog helps you test scope validation and authorization flows.

button

What Are OAuth 2.0 Scopes?

Scopes are permission strings included in OAuth access tokens.

Scope Format

pets:read          - Read pet data
pets:write         - Create/update pets
orders:read        - Read orders
orders:write       - Create orders
admin:all          - Full admin access

Scope in OAuth Flow

1. Authorization Request:

GET /oauth/authorize?
  client_id=app123&
  scope=pets:read orders:read&
  redirect_uri=https://app.com/callback

2. User Consent:

App "PetFinder" wants to:
- Read your pets
- Read your orders

[Allow] [Deny]

3. Access Token:

{
  "access_token": "eyJhbGc...",
  "scope": "pets:read orders:read",
  "expires_in": 3600
}

4. API Request:

GET /v1/pets
Authorization: Bearer eyJhbGc...

200 OK (scope validated)

How Scopes Work

Token Contains Scopes

Access token includes granted scopes:

// Decoded JWT
{
  "sub": "user-456",
  "scope": "pets:read orders:read",
  "exp": 1710331200
}

API Validates Scopes

app.get('/v1/pets', requireScope('pets:read'), async (req, res) => {
  const pets = await getPets();
  res.json(pets);
});

app.post('/v1/pets', requireScope('pets:write'), async (req, res) => {
  const pet = await createPet(req.body);
  res.status(201).json(pet);
});

function requireScope(requiredScope) {
  return (req, res, next) => {
    const token = extractToken(req);
    const decoded = verifyToken(token);

    if (!decoded.scope.includes(requiredScope)) {
      return res.status(403).json({
        error: 'insufficient_scope',
        message: `Requires scope: ${requiredScope}`
      });
    }

    next();
  };
}

Designing Scopes

Scope Naming Conventions

Resource:Action Pattern:

pets:read
pets:write
orders:read
orders:write
users:read
users:write

Granular Scopes:

pets:read
pets:create
pets:update
pets:delete

Wildcard Scopes:

pets:*        - All pet operations
*:read        - Read all resources
admin:*       - Full admin access

Scope Hierarchy

admin:all
  ├── pets:*
  │   ├── pets:read
  │   ├── pets:write
  │   └── pets:delete
  ├── orders:*
  │   ├── orders:read
  │   └── orders:write
  └── users:*
      ├── users:read
      └── users:write

Implementing Scope Validation

Middleware Approach

function requireScopes(...requiredScopes) {
  return (req, res, next) => {
    const token = extractToken(req);
    const decoded = verifyToken(token);
    const tokenScopes = decoded.scope.split(' ');

    const hasAllScopes = requiredScopes.every(scope =>
      tokenScopes.includes(scope) || tokenScopes.includes('admin:all')
    );

    if (!hasAllScopes) {
      return res.status(403).json({
        error: 'insufficient_scope',
        required: requiredScopes,
        provided: tokenScopes
      });
    }

    req.user = decoded;
    next();
  };
}

// Usage
app.get('/v1/pets', requireScopes('pets:read'), getPets);
app.post('/v1/pets', requireScopes('pets:write'), createPet);
app.delete('/v1/pets/:id', requireScopes('pets:delete'), deletePet);

Decorator Approach (TypeScript)

function RequireScopes(...scopes: string[]) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const req = args[0];
      const res = args[1];

      const token = extractToken(req);
      const decoded = verifyToken(token);

      if (!hasScopes(decoded.scope, scopes)) {
        return res.status(403).json({ error: 'insufficient_scope' });
      }

      return originalMethod.apply(this, args);
    };
  };
}

// Usage
class PetsController {
  @RequireScopes('pets:read')
  async getPets(req, res) {
    const pets = await this.petService.findAll();
    res.json(pets);
  }

  @RequireScopes('pets:write')
  async createPet(req, res) {
    const pet = await this.petService.create(req.body);
    res.status(201).json(pet);
  }
}

How Modern PetstoreAPI Uses Scopes

Available Scopes

pets:read          - Read pet data
pets:write         - Create/update pets
pets:delete        - Delete pets
orders:read        - Read orders
orders:write       - Create orders
inventory:read     - Read inventory
inventory:write    - Update inventory
users:read         - Read user profile
users:write        - Update user profile
admin:all          - Full access

Scope Validation

GET /v1/pets
Authorization: Bearer token_with_pets:read

200 OK
POST /v1/pets
Authorization: Bearer token_with_pets:read

403 Forbidden
{
  "error": "insufficient_scope",
  "required": ["pets:write"],
  "provided": ["pets:read"]
}

See Modern PetstoreAPI OAuth documentation.

Testing Scopes with Apidog

Apidog supports OAuth scope testing:

  1. Configure OAuth 2.0 auth
  2. Request specific scopes
  3. Test endpoints with different scopes
  4. Validate 403 responses for insufficient scopes

Best Practices

1. Use granular scopes - pets:read not read_all

2. Follow naming conventions - resource:action format

3. Document all scopes - List in API documentation

4. Validate on every request - Don’t trust client

5. Return clear errors - Show required vs provided scopes

6. Use least privilege - Request minimum scopes needed

Conclusion

OAuth 2.0 scopes provide granular access control. Use resource:action format, validate on every request, and document all scopes. Modern PetstoreAPI demonstrates production-ready scope implementation.

FAQ

What’s the difference between scopes and roles?

Scopes are permissions for access tokens. Roles are user groups with assigned permissions.

Can you have multiple scopes?

Yes, separate with spaces: pets:read orders:read users:write

How do you revoke scopes?

Revoke the access token or issue a new token with different scopes.

Should scopes be in the JWT?

Yes, include in the scope claim for stateless validation.

How granular should scopes be?

Balance granularity with usability. pets:read and pets:write is usually sufficient.

Explore more

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO vs Native WebSocket: Which Should You Use?

Socket.IO adds features like automatic reconnection and fallbacks, but Native WebSocket is simpler and faster. Learn when to use each and how Modern PetstoreAPI implements both.

13 March 2026

When Should You Use MQTT Instead of HTTP for APIs?

When Should You Use MQTT Instead of HTTP for APIs?

MQTT excels for IoT devices with limited bandwidth and unreliable networks. Learn when MQTT beats HTTP and how Modern PetstoreAPI uses MQTT for pet tracking devices and smart feeders.

13 March 2026

WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?

WebSocket vs Server-Sent Events: Which Is Better for Real-Time APIs?

WebSocket and Server-Sent Events both enable real-time communication, but they solve different problems. Learn when to use each and how Modern PetstoreAPI implements both protocols.

13 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs