⚙️ One Action Quietly Ignoring the Authorization Rule Every Other Action Follows
Placing `[Authorize]` on a controller class is supposed to protect every action inside it by default – but a single action explicitly marked `[AllowAnonymous]` (sometimes left over from testing, sometimes added deliberately for one legitimately public endpoint and then forgotten) silently overrides the class-level attribute for just that one method, leaving it reachable without authentication while every sibling action correctly requires it.
🔎 The Problem
[Authorize]
public class AccountController : ControllerBase
{
[HttpGet("profile")]
public IActionResult GetProfile() => Ok(_userService.GetProfile(User));
[HttpPost("reset-password")]
[AllowAnonymous] // Added during early testing, and never removed
public IActionResult ResetPassword(ResetRequest request)
=> Ok(_userService.ResetPassword(request));
// GetProfile correctly requires authentication - ResetPassword does
// NOT, because [AllowAnonymous] on an action always overrides a
// class-level [Authorize], by design. If this endpoint was only
// ever meant to be reachable during development, it'"'"'s now
// reachable by anyone, in production, with no authentication at all.
✅ Fix: Audit Every AllowAnonymous Deliberately
- Searching the entire codebase specifically for `[AllowAnonymous]` and reviewing each occurrence individually – confirming it’s still an intentional, currently-necessary exception rather than a forgotten leftover – is the direct fix, and worth doing as a one-time audit the moment this pattern is suspected anywhere in a codebase.
- For an endpoint that’s genuinely meant to be public, adding a code comment directly above the `[AllowAnonymous]` attribute explaining WHY it’s there turns every future reviewer’s job from re-investigating the decision into just confirming the stated reason still holds.
- A custom analyzer or a simple CI script that flags any new `[AllowAnonymous]` attribute added to a pull request (requiring an explicit reviewer acknowledgment) catches this specific mistake going forward, rather than relying on someone noticing it during a routine code review months later.
⚠️ Why This Is a High-Severity Surprise, Not a Cosmetic One
- This isn’t a bug that produces a visibly broken feature – the endpoint works perfectly from the caller’s point of view, which is exactly the problem: a security gap that functions correctly from the outside gives no natural signal that anything is wrong until someone specifically checks authorization behavior.
- This is worth testing explicitly as part of any security review: attempting to call every action WITHOUT authentication and confirming each one is correctly rejected, rather than trusting that a class-level `[Authorize]` attribute alone guarantees blanket protection.
A class-level Authorize attribute is a default, not a guarantee – one AllowAnonymous anywhere inside it is all it takes to open a single door nobody meant to leave unlocked.
