Bola Idor in Strapi with Bearer Tokens
Bola Idor in Strapi with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API exposes one user’s resource through an ID (or other key) without verifying that the requesting user is authorized to access that specific resource. Strapi, by default, provides a powerful admin API and a public API. When Bearer Tokens are used for authentication, a BOLA vulnerability can arise if token validation is performed but ownership or scope checks are omitted on individual record endpoints.
Consider a Strapi backend exposing a REST or GraphQL endpoint such as /api/users/:id or /api/orders/:orderId. If the API validates the presence of a valid Bearer Token (via an Authorization header like Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...) but does not confirm that the resource identified by :id belongs to the subject represented by the token’s payload (e.g., a user ID or tenant ID), an authenticated attacker can manipulate the ID to access or modify other users’ data. This is a classic BOLA scenario: authentication is present, but authorization is incomplete.
In Strapi, this often maps to the following conditions:
- Authentication via Bearer Token: Strapi plugins or custom policies validate the token and attach a user object to the request. This is correctly implemented when the token is verified and a user record is loaded.
- Missing ownership or tenant scoping: The endpoint does not enforce that, for example, a
userIdin the token matches theidin the URL, or that a content entry belongs to the user’s organization. - Excessive data exposure: Without proper checks, responses may return sensitive fields such as email, password hashes, or internal flags for resources the caller should not see.
Real-world examples include endpoints like GET /api/profiles/:profileId where the profile’s user field is not compared to the authenticated user extracted from the Bearer Token, or GraphQL queries that fetch records by ID without a resolver-level scope check. Attack patterns include IDOR enumeration, unauthorized read/write, and data exfiltration. This intersects with the broader category of BOLA/IDOR in middleBrick’s 12 security checks, which specifically tests for missing resource-level authorization even when authentication mechanisms like Bearer Tokens are in place.
Bearer Tokens-Specific Remediation in Strapi — concrete code fixes
Remediation focuses on ensuring that every request that accesses or modifies a resource validates not only the token, but also that the resource belongs to the authenticated subject. Below are concrete, syntactically correct examples for Strapi v4 (the current major line) using policies and controllers, including Bearer Token handling.
1. Policy that attaches user from Bearer Token
Create a custom policy src/policies/validate-user.js that extracts and verifies the Bearer Token (if using JWT) and ensures the user exists. Strapi’s auth plugins can also be leveraged, but a custom policy gives explicit control.
// src/policies/validate-user.js
'use strict';
/**
* Policy to validate Bearer Token and attach user to request.
* Assumes JWT payload contains a `userId` or `id`.
*/
module.exports = async (ctx, next) => {
const authHeader = ctx.request.header['authorization'] || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (!token) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized', message: 'Missing Bearer Token' };
return;
}
try {
// Replace with your actual JWT verification logic or service
const payload = verifyJwt(token, process.env.JWT_SECRET);
const user = await strapi.entityService.findOne('api::user.user', payload.userId);
if (!user) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized', message: 'Invalid token' };
return;
}
ctx.state.user = user;
await next();
} catch (err) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized', message: 'Invalid or expired token' };
}
};
// Helper JWT verification (example using jsonwebtoken)
function verifyJwt(token, secret) {
const jwt = require('jsonwebtoken');
return jwt.verify(token, secret);
}
2. Controller enforcing ownership on record access
In your controller, compare the authenticated user’s ID with the resource’s user reference before returning or modifying data.
// src/api/order/controllers/order.js
'use strict';
module.exports = {
async find(ctx) {
const { user } = ctx.state; // attached by policy
const { id } = ctx.params;
const order = await strapi.db.query('api::order.order').findOne({
where: { id, user: user.id }, // enforce ownership
});
if (!order) {
ctx.status = 403;
ctx.body = { error: 'Forbidden', message: 'You do not have access to this order' };
return;
}
ctx.body = order;
},
async update(ctx) {
const { user } = ctx.state;
const { id } = ctx.params;
const order = await strapi.db.query('api::order.order').findOne({
where: { id, user: user.id },
});
if (!order) {
ctx.status = 403;
ctx.body = { error: 'Forbidden', message: 'You cannot update this order' };
return;
}
const updated = await strapi.db.query('api::order.order').update({ where: { id }, data: ctx.request.body });
ctx.body = updated;
},
};
3. GraphQL resolver scoping
If using GraphQL, scope the resolver to ensure the record’s user matches the authenticated user.
// src/api/order/resolvers.js
'use strict';
module.exports = {
Query: {
order: async (_, { id }, { state }) => {
const { user } = state;
const order = await strapi.db.query('api::order.order').findOne({
where: { id, user: user.id },
populate: ['products'],
});
if (!order) {
throw new Error('Order not found or access denied');
}
return order;
},
},
};
4. Using middleware for global scoping (optional)
For REST endpoints, Strapi middlewares can enforce tenant or user scoping globally for a route prefix, reducing repetitive checks.
// src/middlewares/scope-by-user/index.js
'use strict';
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
const authHeader = ctx.request.header['authorization'] || '';
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (token) {
try {
const payload = verifyJwt(token, process.env.JWT_SECRET);
ctx.state.user = { id: payload.userId };
} catch (err) {
// allow public endpoints to proceed; handled by controller/policy
}
}
await next();
};
};
These patterns ensure that Bearer Token authentication is paired with explicit ownership checks, effectively mitigating BOLA risks in Strapi APIs. The examples include realistic error handling, use of Strapi’s entity service, and alignment with common authentication libraries.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |