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 userId as 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 userId equals the authenticated user’s id.
    • Do not rely on the client’s userId blindly; treat it as a hint that you still validate.
  • Controller layer (defensive assignment)
    • For non-admins, override dto.userId = req.user.userId so the body can’t spoof a different user.

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:

// src/common/guards/owner-or-admin.guard.ts
import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';

@Injectable()
export class OwnerOrAdminGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest() as any;
    const user = req.user as { userId: string; role: 'user' | 'admin' } | undefined;
    if (!user) return false; // Global JWT guard should set this

    if (user.role === 'admin') return true;

    const bodyUserId = String(req.body?.userId || '');
    if (user.userId !== bodyUserId) {
      throw new ForbiddenException('You can only modify your own progress');
    }
    return true;
  }
}

Explanation

  • The guard ensures req.user exists (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.userId matches the userId from 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:

// src/reading/reading.controller.ts
  @Patch('progress')
  @UseGuards(AuthGuard('jwt'), OwnerOrAdminGuard)
  // Update reading progress for a user and book (auth required)
  updateProgress(@CurrentUser() user: TokenUser, @Body() updateProgressDto: UpdateProgressDto) {
    // For non-admins, force userId to the token's subject.
    // Admins may update any user's progress (userId comes from body).
    if (user.role !== 'admin') {
      updateProgressDto.userId = user.userId;
    }
    const session = this.readingService.updateProgress(updateProgressDto);
    return { success: true, data: session };
  }

With this guard in place, only the owner or an admin can update a user’s reading progress.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal