Security

Authorize is not enough: ownership checks against IDOR in ASP.NET Core

A developer found an IDOR flaw in their own security test, where an endpoint returned records without checking that they belonged to the caller. The article explains why [Authorize] alone cannot cover ownership and how to make the check hard to forget.

DEV Community: #csharp Hüseyin Bozdemir
Published
Reading time
12 min read
agent run #10
agent run #10
Go to original dev.to

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • [Authorize] authenticates the caller; resource ownership needs a separate check.
  • Permission checks and ownership checks are two distinct gates, and a request must pass both.
  • Marker interfaces such as IDepartmentScoped make the ownership level visible in the type system.
  • Load the record and apply the ownership condition in one WHERE query.
  • Overwrite owner ids in request bodies, such as CompanyId, with values from the token.
  • Department lists embedded in a token stay fixed until it expires, so keep token lifetimes short.

A developer found an IDOR flaw during their own security test: an endpoint returned a card to any signed-in user who knew its id, because the ownership check had been written by hand in each controller and was missing from one of them. Authentication itself worked. The [Authorize] attribute only confirms who the caller is, not whether the record belongs to them. The author links this pattern to BOLA, the top item on OWASP's API security list.

The proposed fix is to stop depending on developer memory. Entities implement marker interfaces such as IDepartmentScoped so the ownership level is visible in the type system, and a generic guard loads a record with its ownership condition in a single WHERE query, so foreign rows never reach memory. The article also notes that a global query filter alone is not enough, since IgnoreQueryFilters() and raw SQL bypass it, and that department lists stored in a token stay valid until it expires.

Real-world use

  • A corporate card or expense API where records belong to companies and departments.
  • A multi-tenant backend whose endpoints accept resource ids and must check ownership on every action.
  • Microservices and message consumers that receive resource ids and must verify them against their own data.

Why it matters

Any new endpoint that loads a record outside the ownership path reopens the IDOR gap. Placing the check inside the data access call keeps it from depending on each developer remembering it.

This summary was generated by the agent from the original post on DEV Community: #csharp. The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message