TL;DR
OAuth 2.0 scopes are permission strings that define what an access token can do. Use a format such as resource:action, for example pets:read or orders:write. Request scopes during authorization and validate them on every protected API endpoint. Modern PetstoreAPI uses scopes for read and write access to pets, orders, inventory, and user data.
Introduction
A third-party app wants to read your pet store’s inventory. Should it also be allowed to create orders, delete pets, or manage users? No. It should receive only the permissions it needs.
OAuth 2.0 scopes provide this access control. The app requests an inventory:read scope, and your API checks that the access token includes the scope before returning inventory data.
Modern PetstoreAPI implements granular scopes for 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 associated with OAuth access tokens. They define which operations the token can perform.
Scope format
A resource:action format makes scopes easy to understand and validate:
pets:read - Read pet data
pets:write - Create or update pets
orders:read - Read orders
orders:write - Create orders
admin:all - Full admin access
How Scopes Work in the OAuth Flow
1. Request scopes during authorization
The client includes the requested scopes in the authorization request:
GET /oauth/authorize?
client_id=app123&
scope=pets:read orders:read&
redirect_uri=https://app.com/callback
2. Display the requested permissions
The authorization server shows the user what the application is requesting:
App "PetFinder" wants to:
- Read your pets
- Read your orders
[Allow] [Deny]
3. Return granted scopes in the access token
The token response includes the scopes granted to the client:
{
"access_token": "eyJhbGc...",
"scope": "pets:read orders:read",
"expires_in": 3600
}
The granted scopes can be narrower than the scopes originally requested if the user or authorization server denies some permissions.
4. Send the access token to the API
The client includes the token in the Authorization header:
GET /v1/pets
Authorization: Bearer eyJhbGc...
The API verifies the token and checks whether it contains pets:read before returning the response.
Store Scopes in the Access Token
For a JWT access token, scopes are commonly stored in the scope claim:
{
"sub": "user-456",
"scope": "pets:read orders:read",
"exp": 1710331200
}
Before checking permissions, your authentication middleware should:
- Extract the bearer token.
- Verify the token signature and expiration.
- Read the
scopeclaim. - Reject the request if the required scope is missing.
Validate Scopes on API Endpoints
Each protected endpoint should declare the scope it requires:
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);
}
);
A simple requireScope middleware can perform the check:
function requireScope(requiredScope) {
return (req, res, next) => {
const token = extractToken(req);
const decoded = verifyToken(token);
const tokenScopes = decoded.scope.split(' ');
if (!tokenScopes.includes(requiredScope)) {
return res.status(403).json({
error: 'insufficient_scope',
message: `Requires scope: ${requiredScope}`
});
}
next();
};
}
A request with a valid token but insufficient permissions should return 403 Forbidden:
{
"error": "insufficient_scope",
"message": "Requires scope: pets:write"
}
Authentication and authorization are separate checks:
- Return
401 Unauthorizedwhen the token is missing, invalid, or expired. - Return
403 Forbiddenwhen the token is valid but lacks the required scope.
Design a Scope Naming Convention
Use the resource:action pattern
Start with a consistent naming convention:
pets:read
pets:write
orders:read
orders:write
users:read
users:write
Choose between broad and granular actions
Broad scopes are easier to manage:
pets:read
pets:write
More granular scopes provide tighter control:
pets:read
pets:create
pets:update
pets:delete
Choose the level of granularity based on the operations your clients need to perform. Avoid creating separate scopes when the additional distinction does not provide a meaningful security benefit.
Define wildcard or administrator scopes carefully
Some systems define broader scopes:
pets:* - All pet operations
*:read - Read all resources
admin:* - Full admin access
If you support wildcard scopes, document how they expand and ensure the validation logic handles them consistently.
Model Scope Hierarchies
A scope hierarchy can describe broader permissions and their narrower capabilities:
admin:all
├── pets:*
│ ├── pets:read
│ ├── pets:write
│ └── pets:delete
├── orders:*
│ ├── orders:read
│ └── orders:write
└── users:*
├── users:read
└── users:write
For example, an admin:all token can be treated as satisfying resource-level checks. Make this behavior explicit in your authorization rules rather than assuming that every scope hierarchy will be understood automatically.
Implement Multiple-Scope Validation
Some endpoints require more than one scope. A reusable middleware can accept multiple required scopes:
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();
};
}
Use the middleware when registering routes:
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
);
The example uses an all-scopes rule: the token must contain every required scope unless it contains admin:all. If your API needs an any-scopes rule, implement that separately and document the difference.
Validate Scopes with a TypeScript Decorator
In a TypeScript application, authorization can also be applied with a decorator:
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);
};
};
}
Apply the decorator to controller methods:
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);
}
}
Whichever implementation style you choose, keep token verification and scope checks centralized so that routes do not implement authorization inconsistently.
How Modern PetstoreAPI Uses Scopes
Available scopes
Modern PetstoreAPI defines the following scopes:
pets:read - Read pet data
pets:write - Create or 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 example
A token with pets:read can access the read endpoint:
GET /v1/pets
Authorization: Bearer token_with_pets:read
200 OK
The same token cannot create a pet because it does not include pets:write:
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.
Test OAuth Scopes with Apidog
Use an API client such as Apidog to test both successful and failed authorization cases.
A practical scope test plan includes:
- Configure OAuth 2.0 authentication.
- Request a token with
pets:read. - Call
GET /v1/petsand verify that it succeeds. - Call
POST /v1/petswith the same token. - Verify that the API returns
403 Forbidden. - Request a token with
pets:write. - Call
POST /v1/petsagain and verify the expected success response. - Test expired, malformed, and missing tokens separately.
This tests both the OAuth flow and the endpoint-level scope enforcement.
Best Practices
Use granular scopes. Prefer
pets:readover a broad permission such asread_all.Follow a naming convention. Use a consistent
resource:actionformat.Document every scope. List available scopes and the endpoints that require them in your API documentation.
Validate every request. Do not trust the client to enforce permissions.
Return clear errors. Include the required and provided scopes where appropriate.
Apply least privilege. Request and grant only the minimum scopes a client needs.
Test both positive and negative cases. Confirm that allowed operations succeed and insufficient scopes produce
403responses.
Conclusion
OAuth 2.0 scopes provide granular access control for APIs. Use a consistent resource:action naming scheme, include granted scopes in access tokens, validate them on every protected request, and document the permissions available to clients.
Modern PetstoreAPI demonstrates this approach with separate scopes for pets, orders, inventory, and user data.
FAQ
What’s the difference between scopes and roles?
Scopes are permissions associated with access tokens. Roles are user groups with assigned permissions. A role can be used to determine which scopes a user or client may receive, while the API uses the token’s scopes to authorize individual requests.
Can you have multiple scopes?
Yes. Separate multiple scopes with spaces:
pets:read orders:read users:write
How do you revoke scopes?
Revoke the access token or issue a new token with a different set of scopes.
Should scopes be in the JWT?
Scopes can be included in the JWT scope claim for stateless validation. Your API must still verify the token before trusting the claim.
How granular should scopes be?
Balance granularity with usability. pets:read and pets:write are often sufficient, but separate scopes such as pets:create, pets:update, and pets:delete may be useful when clients need more narrowly defined permissions.
Top comments (0)