Enforcing Ownership Controls
Introduction: Why Ownership Matters in APIs
Welcome to the first lesson of this course on enhancing your API with guards and interceptors. In this lesson, we will focus on a very important topic: enforcing ownership when users update their reading progress.
In the previous course, we promised to close a serious gap: any logged-in user could modify someone else’s reading progress. That’s not okay. In this unit you’ll enforce ownership so users can only update their own progress, while admins keep the ability to update anyone’s (for support/moderation).
You already have:
- JWT authentication (via
JwtAuthGuard) — verifies the request has a valid token. - Role-based authorization (via
RolesGuard) — enforces admin vs user.
Now we add ownership checks so the API matches the updated UI: regular learners can’t edit each other’s progress; admins can. The SPA included with this course respects the same rules (controls are disabled when you’re not allowed to edit).
In this lesson, you will learn how to use NestJS features to make sure only the right users can update their own reading progress or, in some cases, allow an admin to update progress for anyone.
The vulnerability we’re closing
Before: JwtAuthGuard let any authenticated user call write endpoints like PATCH /reading/progress. That meant “Alice” could submit {"userId": 3, ...} and update “Bob’s” progress.
Now: Only:
- the owner (the same
userIdas the token) or - an admin may update a user’s reading progress.
We’ll implement this with a dedicated guard and a tiny controller change to avoid trusting client-provided userId.
Here is what we’ll do:
- Guard layer (
OwnerOrAdminGuard)- If admin → allow.
- If user → only allow when the target
userIdequals the authenticated user’s id. - Do not rely on the client’s
userIdblindly; treat it as a hint that you still validate.
- Controller layer (defensive assignment)
- For non-admins, override
dto.userId = req.user.userIdso the body can’t spoof a different user.
- For non-admins, override
This two-step pattern prevents mistakes if new endpoints are added later.
How OwnerOrAdminGuard Solves This
To solve this problem, we use a guard in NestJS called OwnerOrAdminGuard. A guard is a special class that runs before your controller logic. It can allow or block the request based on custom rules.
Here is the code for the guard:
Explanation
- The guard ensures
req.userexists (set by the JWT strategy). - If the user is an admin → request is allowed.
- If the user is not an admin → checks if
req.body.userIdmatches theuserIdfrom the JWT. - If they don’t match, it throws a ForbiddenException.
This guarantees that only the owner (or an admin) can make changes.
How it is used in the controller:
With this guard in place, only the owner or an admin can update a user’s reading progress.
