skip to content

Failure Precedence Rules

Which failure wins: 401 vs 403 vs 404 order, where a 404-for-403 policy is enforced, validation before or after authorization, why hook order decides. Asked because the wrong order leaks what exists.

on this pageshow

questions

5

In a server-side web framework, in what order do authentication, authorization and existence checks normally run?

level: middleimportance: must knowfreq 62%

answer

  1. first refusal wins
  2. stage order, not check severity
  3. credential, then rule, then row
  4. existence needs a lookup first
  5. 401 before 403 before 404

basics

~20 s

Authentication runs first in a pre-handler stage, authorization next, existence last inside the handler after the lookup. The earliest stage to refuse writes the response, so an anonymous request for a missing record answers 401, not 404.

solid answer

~40 s

The status is chosen by the **first** stage that refuses, not by the deepest or most specific check. A typical chain runs transport handling, route resolution, an authentication hook that answers `401` when no valid credential is present, a coarse authorization hook that answers `403` for an identified caller lacking the right, then the handler, which loads the record and answers `404` when it is absent. So the default precedence is `401` over `403` over `404`, and it is a consequence of ordering rather than a policy anyone wrote down: existence of a specific record is the last fact the server learns, because only the handler performs the lookup. Move a check to a different stage and the precedence moves with it.

go deeper

for a junior

Remember the shape: credentials are checked before rules, and rules before the database lookup. If you send no credential, expect 401 even for a URL that does not exist.

for a middle

Explain the mechanism, not the list: each stage refuses on what it can already see, and the first refusal ends the request. Be ready to say why existence is the last fact available.

for a senior

Show you have debugged this on a live service: name the surprising cases, such as an unregistered path answering 401 or an ownership rule that cannot run before the load, and say how you confirmed the real order.

for a principal

Treat precedence as observable API behaviour that belongs in a contract. Argue the tradeoff between refusing early for cost and safety and deciding late for a more useful answer, and who owns that decision across teams.

Failure precedence is the question of which error a client sees when several things are wrong with one request at once: the caller sent no credential, *and* lacks the right, *and* asked for a record that is not there. The answer is decided by the shape of the request pipeline, not by a table of status codes. ## Precedence is a property of the pipeline A server-side web framework handles a request as a chain of stages: connection and protocol handling, request parsing, route resolution, any number of pre-handler hooks (the names differ - middleware, interceptors, hooks), the handler itself, then the outbound stages that write the response. Any stage may stop the request and produce the response on its own; when one does, every later stage is skipped. That single fact produces precedence. **When several failures apply, the client sees the one found by the earliest stage** - not the most severe, not the most informative, not the one the API designer would have chosen. If you want a different answer, you move the check to a different stage; changing the status code alone does not change which check ran first. ## What each stage is able to know A stage can only refuse on facts it already holds. | Stage | What it knows | Typical refusal | |---|---|---| | Transport and body limits | byte counts, declared media type | `413`, `415` | | Route resolution | whether a path template matched | built-in not-found | | Authentication hook | whether a credential is present and valid | `401` | | Coarse authorization hook | the caller's roles or scopes against the route | `403` | | Binding and validation | whether the body fits the declared shape | `400`, `422` | | Handler, after the lookup | whether the record exists, and who owns it | `404`, `403` | Read the last row carefully: **the existence of a specific record is the last fact the server learns.** A pre-handler hook holds the caller's identity, the matched route and the path values; it does not hold the row. This is why ownership rules are hard to enforce ahead of the handler, and why an identity-dependent `404` cannot be produced before the load. ## The default order, step by step 1. The request is parsed far enough to be routed. 2. Globally mounted hooks run. In many frameworks a hook mounted this way runs *before* route resolution, so an unregistered path and a registered one are treated alike at this point. 3. The route is resolved, and route-scoped hooks run - the usual home of a coarse rights check. 4. The body is bound and validated as the handler is dispatched. 5. The handler loads what it needs and applies the rules that depend on loaded data. The precedence that falls out for the common case is `401`, then `403`, then `404`. ## Why that order is defensible - A rule such as "the owner may read this" has no subject to evaluate until a caller has been identified, so authentication genuinely has to come first. - Refusing early is cheap. A request that fails the credential check never touches the data store, which matters when unauthenticated traffic arrives in volume. - Answering `401` for an unregistered path leaks less than answering not-found, because an anonymous scanner cannot separate the paths that exist from the ones that do not. - The order degrades safely: each stage refuses on less information than the next, so a mistake at an early stage produces a refusal, not an accidental disclosure. ## Where the order changes - Mounting the authentication check per route instead of globally moves it behind route resolution: an unregistered path then reaches the framework's built-in not-found response with no credential check at all. - Moving an authorization check inside the handler, after the load, is the only position from which it can see both existence and permission and choose between them. - Frameworks differ in whether a globally registered hook runs before or after route resolution, and in whether body binding happens before or after route-scoped hooks. The precedence you have is the one your chain produces, so confirm it by sending a request rather than by reading the registration code. ## Consequences worth saying out loud - **Enumeration.** If existence is decided before rights, status codes tell an attacker which identifiers exist. - **One failure at a time.** Short-circuiting means a caller fixes one problem, retries, and meets the next; there is no combined report. - **Diagnosis.** The wire status names the stage that refused, not the root cause, so the real reason belongs in a log line carrying the correlation id. - **Cost.** Every check moved later is work done on behalf of a caller who was going to be refused anyway.

  • Why can a pre-handler authorization hook rarely enforce an ownership rule on its own?
    Because ownership is a property of the stored record and nothing has loaded it yet. The hook sees the caller, the matched route and the path values, so it can enforce role or scope rules; deciding whether this caller owns the record behind that identifier needs the lookup, which is the handler's work. A hook that triggers the load itself is a handler in all but name.
  • If two stages would both refuse a request, does the client ever learn about the second one?
    Not in a short-circuiting chain: the first refusal writes the response and the remaining stages never run, so the second failure is never evaluated. That is why a caller who fixes the credential can immediately meet a rights failure on the retry, and why negative paths have to be reasoned about one stage at a time.
  • Does the same precedence apply when the failure is raised as an exception rather than written directly?
    Yes in effect: the stage that raises first is still the stage that decides, because raising also unwinds the chain. What a central failure-to-response component adds is a uniform shape for the answer; it does not re-run the checks that were skipped, and it cannot restore facts the skipped stages would have produced.

saying these in an interview costs you the question

  • Says the handler picks the status even when a pre-handler hook already refused
  • Claims 404 wins because the lookup runs before any rights check
  • Expects 404 for a missing record when no credential was sent at all
  • Thinks a pre-handler hook can check record ownership without loading the record
  • Treats the order as fixed by the framework and impossible to change
open as a page

If you answer 404 rather than 403 to hide a record's existence, at which pipeline stage must that decision be made?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Make it where both facts are known: in the handler, after the record loads and the visibility rule runs. A pre-handler hook cannot mask what it never loaded, and rewriting every refusal into 404 over-masks failures unrelated to hidden records.

open as a page

On an API where every route requires a credential, does a request to an unregistered path answer 401 or not-found?

level: juniorimportance: should knowfreq 42%

basics

~20 s

It depends where the credential check is mounted. Mounted globally, ahead of route resolution, it answers 401 for any path; attached per route, it never runs for an unregistered path and the built-in not-found response wins.

open as a page

Should request validation run before or after authorization in a web framework's pipeline, and what does each order give away?

level: middleimportance: should knowfreq 50%

basics

~20 s

Authorize first for anything a stranger can reach. A validation error returned before the rights check hands an unauthorized caller your field names, accepted values and sometimes which identifiers exist; validating first only buys a friendlier error.

open as a page

How would you keep failure precedence uniform across an edge gateway and dozens of independently built backend services?

level: principalimportance: should knowfreq 33%

basics

~20 s

Write the order down as a contract - credential, then rights, then existence - settle credentials once at a shared edge, and verify every endpoint against it. The one service that answers differently becomes the enumeration oracle.

open as a page