TL;DR
OAuth 2.0 scopes are permission strings that define what an access token can do. Use the format resource:action, such as pets:read or orders:write. Request scopes during authorization, and validate them on your 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. For example, the app requests the inventory:read scope, and your API only returns data if the access token includes this scope.
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.
What Are OAuth 2.0 Scopes?
Scopes are permission strings included in OAuth access tokens. They restrict what an access token can do.
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
The access token itself contains the granted scopes, often as a space-delimited string.
// Decoded JWT
{
"sub": "user-456",
"scope": "pets:read orders:read",
"exp": 1710331200
}
API Validates Scopes
Check required scopes on every endpoint using middleware:
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
Implement a reusable middleware for scope validation:
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)
For TypeScript, use decorators for cleaner controller logic:
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:
- Configure OAuth 2.0 auth
- Request specific scopes
- Test endpoints with different scopes
- Validate 403 responses for insufficient scopes
Best Practices
-
Use granular scopes – Prefer
pets:readoverread_all -
Follow naming conventions – Use
resource:actionformat - Document all scopes – List every scope in your API documentation
- Validate on every request – Never trust the client
- Return clear errors – Show required vs. provided scopes
- Use least privilege – Request only the 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 them 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 scopes in the scope claim for stateless validation.
How granular should scopes be?
Balance granularity and usability. Usually, pets:read and pets:write are sufficient.
Top comments (0)