Skip to main content
ANVISoftware Solutions
Lesson 5 of 23Intermediate18 min

Middleware Order

By the end of this lesson

Order middleware correctly, and diagnose problems caused by getting it wrong.

In the pipeline, order is not a matter of style. It is the behaviour of the application. The same six lines in a different sequence give you a different API, and the difference usually surfaces either as an error that makes no sense or as a security hole that makes no noise at all.

One rule explains every specific case: a component can only act on information that already exists. Authorization cannot check permissions before authentication has established who is asking. Nothing can read an endpoint's metadata before routing has selected an endpoint. Exception handling can only catch what happens after it.

This lesson is the one to come back to. Most confusing behaviour in an ASP.NET Core application is an ordering problem wearing a disguise.

The middleware pipelineA request passes through middleware components in the order they are registered: exception handling, HTTPS redirection, routing, CORS, authentication, authorization, then the endpoint. The response travels back out through the same components in reverse order. Because each component can act before and after the next one, registration order determines behaviour.ExceptionhandlingHTTPSredirectionRoutingCORSAuthenticationAuthorizationRequest in →← Response outEndpointAuthentication must come before authorization — you cannot check permissionsbefore you know who is asking. Order here is behaviour, not configuration detail.
Each component wraps the ones registered after it, so the response passes back out through every component that let it through.
An order that works, with the reason for each position
C#
var app = builder.Build();

app.UseExceptionHandler("/error");   // outermost, so it wraps everything below
app.UseHttpsRedirection();
app.UseStaticFiles();                // public assets, no credential needed
app.UseRouting();                    // decides which endpoint this request is for
app.UseCors("directory-clients");
app.UseAuthentication();             // who is calling?
app.UseAuthorization();              // are they allowed to do this?
app.MapControllers();

app.Run();
  • UseExceptionHandler goes first because it is a try and catch around everything registered after it. Anything above it is outside the net.
  • HTTPS redirection comes early, so a request that is about to be redirected does not have work done on it first.
  • Static files sit before authentication deliberately. These files are public, and serving them without running authentication saves that work on every image, stylesheet and script.
  • UseRouting selects the endpoint and records it on the context. Every component that needs to know which endpoint was matched has to be registered after this line.
  • CORS runs after routing so it can read the policy attached to the chosen endpoint, and before authentication so that a rejected request still carries CORS headers on the way back.
  • Authentication, then authorization, in that order, always. The first populates the user on the context; the second inspects it and the endpoint's requirements.
  • MapControllers is last so the file reads in the order the pipeline runs. Endpoint execution is appended to the end of the pipeline regardless of where the Map call appears, which is a detail worth knowing and not worth relying on.
The same components, reordered — four separate faults
C#
var app = builder.Build();

app.UseAuthorization();              // fault 1: nothing has identified the caller yet
app.UseAuthentication();
app.UseRouting();                    // fault 2: the security middleware ran before this
app.UseCors("directory-clients");    // fault 3: after the middleware that can reject
app.UseExceptionHandler("/error");   // fault 4: wraps almost nothing
app.MapControllers();

app.Run();
  • Nothing here fails to compile, and the application starts cleanly. Every fault is a behaviour, discovered later by somebody debugging a symptom.
  • Two of these faults lock valid callers out, which you will hear about quickly. One of them lets callers through, which you may not hear about at all.

The orderings that go wrong in practice, and what you actually observe:

Authorization before authentication
Symptom: every request to an endpoint marked with an authorize attribute comes back 401, including ones with a perfectly valid token. Cause: the authorization middleware reads the user from the context before anything has populated it, so every caller looks anonymous. Teams lose hours checking token configuration when the fix is swapping two lines.
Authentication and authorization before routing
Symptom: none. Protected endpoints happily answer anonymous callers. Cause: the authorization middleware enforces the policy attached to the matched endpoint, and before routing has run there is no matched endpoint, so it finds no policy to enforce. Nothing fails, nothing is logged, and the attribute you added has no effect. This is the dangerous one.
CORS after middleware that can reject a request
Symptom: browser calls fail with a CORS message while your server log shows 401s. Cause: a response produced before the CORS middleware was reached never passes through it, so it goes back without the allow-origin header, and the browser reports the missing header rather than the real status. Put CORS after routing and before authentication.
Exception handling registered late
Symptom: a failure in an earlier component returns a raw error, an empty response, or a stack trace in production. Cause: the exception handler only wraps what is registered after it. Everything above it is unprotected.
Static files after authentication
Symptom: nothing breaks, and every request for a logo or a stylesheet runs the authentication work first. Cause: the pipeline is doing avoidable work on requests that need no credential. This is a cost rather than a bug, which is why it survives for years.

Summary

  • Order is behaviour: the same components in a different sequence make a different application
  • A component can only act on information that already exists, which is what fixes the correct order
  • Exception handling first, routing before anything endpoint-aware, CORS before anything that can reject, authentication before authorization, endpoints last
  • Authorization before authentication returns 401 to valid callers; authorization before routing ignores the authorize attribute entirely and reports nothing
  • A short-circuited response never reaches components registered deeper, so those components cannot add their headers to it

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Swap two lines and watch it break

In a project with one endpoint that requires authentication, swap UseAuthentication and UseAuthorization.

Call the endpoint with a valid credential and record the status code. Then put the lines back and call it again.

Show solution

You get a 401 with a valid credential, because the authorization middleware ran while the user on the context was still anonymous.

This failure is the kind you want: loud, immediate and pointing at the request you just made. Practise recognising it, because the shape repeats. A 401 that survives a known-good token is almost always about order rather than about the token.

The reason for doing this deliberately is the contrast with the opposite mistake. Moving both lines above UseRouting produces no error at all and no 401, and the endpoint answers anonymous callers. Nothing in the output would have told you.

Think about it

A CORS error that is not a CORS problem

Your API serves a browser application. Users report a CORS error in the console. The server log shows a healthy number of 401 responses and nothing about CORS.

What is the most likely arrangement of your pipeline, and what is the browser message actually telling you?

Show solution

CORS is almost certainly registered after authentication. The 401 was produced before the request reached the CORS middleware, so the response never passed through it and went back without the allow-origin header.

The browser is reporting the only thing it can see. It refuses to hand a cross-origin response to the page without that header, so it reports the missing header and the real 401 never reaches the console.

Moving CORS to just after UseRouting makes the 401 visible, and that is all it does. The authentication problem is still there. This is worth internalising: ordering mistakes often hide other problems rather than creating them, so a fix that changes the error message deserves a second look.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

Every request with a valid token gets 401 from endpoints marked with an authorize attribute. Which cause is most likely?
Protected endpoints are answering anonymous callers. UseAuthentication is correctly registered before UseAuthorization. What else should you check?

Saved in this browser only.