Skip to content
QSWEQB
mediumScenarioConceptMidSenior

You added an authorisation policy to an ASP.NET Core endpoint and it is not being enforced. Where do you look?

The pipeline is an ordered list you assembled, and authorisation cannot decide anything before routing has selected an endpoint to decide about. Registered in the wrong order it either runs against nothing or never runs at all, and neither produces an error - the request simply succeeds.

3 min readUpdated 2026-07-26

What the interviewer is scoring

  • Whether the candidate knows routing must precede authorisation and can say why
  • That the pipeline is understood as ordered registration rather than configuration
  • Does the candidate distinguish authentication from authorisation in the ordering
  • Whether short-circuiting and the response unwinding back out are understood
  • That a missing terminal call is recognised as a cause of a silently unenforced policy

Answer

The pipeline is a list, and you wrote the order

A request enters a chain of components assembled in Program.cs. Each one may inspect the request, hand it to the next, inspect the response on the way back, or answer immediately and stop. There is no priority system and no dependency resolution: the order of your Use... calls is the order of execution.

That makes ordering a functional decision that looks like formatting, which is why this class of bug is common and why it produces no error. A policy that never runs is not an exception; it is a request that succeeded.

var app = builder.Build();

app.UseExceptionHandler("/error");   // outermost, so it can catch what follows
app.UseHttpsRedirection();
app.UseStaticFiles();                // before routing: no need to match a route

app.UseRouting();                    // selects the endpoint
app.UseAuthentication();             // establishes who the caller is
app.UseAuthorization();              // decides whether they may reach that endpoint

app.MapControllers();                // terminal: runs the selected endpoint
app.Run();

Why routing has to come first

UseRouting is the component that matches the request against your route table and attaches the selected endpoint to the HttpContext. Everything downstream can then ask what was matched.

UseAuthorization works by reading that endpoint's metadata — the [Authorize] attribute, the policy name, the required roles. Placed before UseRouting, no endpoint has been selected yet, so there is no metadata to read, and the middleware finds nothing to enforce and passes the request through. Silently. The attribute is present, the policy is registered, and the request succeeds.

Authentication sits between them because authorisation needs an identity to judge. Reversed, authorisation evaluates against an unauthenticated principal and rejects callers who are in fact signed in — the same class of bug producing the opposite symptom, and easier to notice precisely because someone complains.

The order is therefore forced by what each step needs, not chosen: match the endpoint, establish who is asking, decide whether they may.

The other way it silently does not run

The second cause of an unenforced policy is a middleware earlier in the chain that never calls next.

app.Use(async (ctx, next) =>
{
    if (ctx.Request.Path.StartsWithSegments("/health"))
    {
        ctx.Response.StatusCode = 200;
        return;                     // deliberate short-circuit; nothing below runs
    }
    await next(ctx);                // omit this and the whole pipeline stops here
});

Short-circuiting is a feature — it is how static files and health checks avoid the cost of everything downstream — and it is indistinguishable from the bug where someone forgot the await next(ctx) on a path. The tell is that the endpoint returns something plausible without your handler ever being hit, so a breakpoint in the controller is the fastest diagnosis.

Requests go down, responses come back up

The mental model that makes custom middleware behave is that each component wraps the rest. Code before await next runs on the way in, code after it runs on the way out.

app.Use(async (ctx, next) =>
{
    var started = Stopwatch.GetTimestamp();
    await next(ctx);                                  // everything downstream
    var elapsed = Stopwatch.GetElapsedTime(started);  // now the response exists
    logger.LogInformation("{Path} {Status} {Ms}ms",
        ctx.Request.Path, ctx.Response.StatusCode, elapsed.TotalMilliseconds);
});

This is also why exception handling goes at the very top: it can only catch what happens inside the part of the chain it wraps, and anything registered above it is outside its reach.

One constraint follows from the same picture and catches people out. Headers are sent with the first byte of the body, so once a downstream component has begun writing, the response has already started and setting a header on the way out throws. A middleware that wants to add a header must do it before next, or register a callback on OnStarting.

What to check, in order

When a policy is not being enforced, the sequence that finds it quickly:

Confirm UseRouting precedes UseAuthentication, which precedes UseAuthorization. Confirm a terminal call such as MapControllers exists and is after all three. Put a breakpoint or a log line in the action itself to establish whether the endpoint is reached at all, which separates "policy ignored" from "request never got there". Then check for a custom middleware registered above authorisation that returns without calling next for this path.

Four checks, and the first one is right most of the time.

The pipeline has no notion of priority. Authorisation cannot judge an endpoint that routing has not yet selected, and nothing will tell you — the request just works.

Likely follow-ups

  • What does UseRouting actually do that UseEndpoints then depends on?
  • Your custom middleware never calls next. What happens to everything after it?
  • Where would you put exception handling, and why there?
  • How would you write a middleware that measures duration correctly?

Related questions

Further reading

aspnet-coremiddlewareauthorisationroutingpipeline