Script Valley
JWT & Session Auth: Deep Dive
Role-Based Access ControlLesson 5.3

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

Resource Ownership Check

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.

Up next

storing roles in JWT claims vs database lookups

Sign in to track progress

resource ownership checks: can this user edit this post โ€” Role-Based Access Control โ€” JWT & Session Auth: Deep Dive โ€” Script Valley โ€” Script Valley