Skip to content

Always authorize on an object loaded from the DB, not on user input

Load the resource from the DB and authorize against it — never trust IDs from the request body alone.

nestjs-security-authorize-from-db-object

Why it matters

Failure modes if this rule is ignored
StakeIf ignored
Data leak
  • Authorizing on input = trusting the user. They can send another tenant's ID.
  • IDOR (Insecure Direct Object Reference) is the most common vulnerability. The order — fetch then authorize — neutralizes it.

How to fix

The user sends siteId. You don't authorize on the ID — you load the site and authorize on the loaded object. If it doesn't exist — NotFoundException. Only then check whether the user may act on it.

Examples

Bad
ts
async deleteSite(siteId: string, user: User) {
  if (!user.canDeleteSites) throw new ForbiddenException();   // ← generic permission
  await this.repo.delete(siteId);                              // ← deletes any site
}
Good
ts
async deleteSite(siteId: string, user: User) {
  const site = await this.repo.findById(siteId);
  if (!site) throw new NotFoundException();
  if (!this.policy.canDelete(user, site)) throw new ForbiddenException();
  await this.repo.delete(siteId);
}

Contribute

Released under the MIT License.

esc