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.
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.
Related stories
- Six common ASP.NET Core Web API mistakes and the patterns to use insteadDEV Community: #aspnetcoreOct 23
- A secure enterprise chat with React, .NET 10 and Microsoft FoundryDEV Community: #dotnet1 d ago
- ASP.NET Core Authentication & Authorization Explained: What to Use, When, and How to Ship It SafelyDEV Community: #aspnetcore1 d ago