resource ownership checks: can this user edit this post
ownership verification pattern, database lookup in middleware, param-based ownership, separating ownership from admin override, async middleware, race condition awareness
Resource Ownership Checks
Permission post:update:own means the user can only update resources they own. Role checks alone are not enough โ you must fetch the resource and compare ownership.
async function requireOwnerOrAdmin(req, res, next) {
const post = await Post.findById(req.params.id);
if (!post) return res.status(404).json({ error: 'Post not found' });
const isOwner = post.authorId === req.user.id;
const isAdmin = req.user.role === 'admin';
if (!isOwner && !isAdmin) {
return res.status(403).json({ error: 'You can only edit your own posts' });
}
req.resource = post; // attach for use in handler โ avoid second DB lookup
next();
}
app.put('/posts/:id', requireAuth, requireOwnerOrAdmin, async (req, res) => {
await req.resource.update(req.body); // use already-fetched resource
res.json({ success: true });
});Attach the fetched resource to req.resource in the middleware so the route handler does not make a second database trip. This is both a performance optimization and a common pattern in production codebases.
Do not skip the 404 check. Returning 403 for a non-existent resource leaks the existence of that resource to unauthorized users โ an information disclosure vulnerability. Return 404 if the resource does not exist, 403 if it exists but access is denied.
