Identity and security
Authorization: roles, policies and ownership
The difference between 401 and 403, and the most skipped check: is this resource really yours?
Understand the concept
Authentication establishes who you are; authorization decides what you may do. When an unauthenticated request reaches a protected resource the response is 401 (prove who you are first). An authenticated user without permission gets 403 (we know you, but no). ASP.NET Core does this automatically: an anonymous user gets a challenge, an unauthorised one a forbid.
Role-based authorization ([Authorize(Roles = "Admin")]) suffices for simple cases. For more flexible rules, policies are used: a policy can evaluate claims, the resource itself or custom logic. Policies are defined once in Program.cs and attached to endpoints by name.
The most common mistake is skipping object-level authorization (BOLA / IDOR). For ‘/api/invoices/42’ it is not enough that the user is signed in; invoice 42 must belong to that user. Otherwise a user reaches other people’s data just by changing the number in the URL. The check is done by adding an ownership condition to the query itself.
Missing object-level authorization is the most common API vulnerability.
Adding [Authorize] alone without checking who owns the resource.
A letter’s journey through the city
Think of a request as a letter and the server as a city. Pick a scenario, press play and watch which gates the letter passes and where it gets stopped.
Someone asks for admin messages without signing in.
One topic. Three levels of depth.
[Authorize(Roles = "Admin")]
[HttpDelete("{id:int}")]
public Task<IActionResult> Delete(int id, CancellationToken ct) => …Only users in the Admin role can delete.
This page connects to no real server or database. Code samples are illustrative for .NET 8, Next.js and SQL Server; timings are example values in the simulation, not measurements. Try samples only in your own development environment; never share real connection strings, tokens or passwords.
Try it yourself first.
User A sees their order at /api/orders/1001. Typing 1002 opens user B’s order. The endpoint is protected with [Authorize]. What is the problem and the fix?
Show the solution and explanation
Sorun: nesne düzeyinde yetkilendirme yok (BOLA/IDOR).
Düzeltme: sorguya OwnerId == currentUserId koşulu ekle,
eşleşmezse 404 dön. Kimliği istemciden değil token’dan/oturumdan al.[Authorize] only answers ‘is the user signed in’; the code must ask ‘is this record theirs’.