In ASP.NET Core, middleware components are the building blocks of your application’s request pipeline. Think of them as a series of checkpoints on an assembly line. Each request passes through these checkpoints in a specific order, and each one has a chance to inspect, modify, or even short-circuit the request.
While this architecture is incredibly powerful, it has a critical dependency: order matters. A lot. Arranging your middleware in the wrong sequence can lead to security vulnerabilities, broken functionality, and hours of frustrating debugging. Let’s dive into the most common ordering pitfalls that can silently break your APIs and how to fix them.
The Request Pipeline: A Two-Way Street
Before we get to the pitfalls, it’s essential to understand how the pipeline works. When an HTTP request arrives, it travels “down” the pipeline, passing through each middleware component in the order they were registered in Program.cs.
Once the request hits the end (usually your API endpoint), the response is generated and travels back “up” the pipeline in the reverse order. This two-way flow is why some middleware can act on both the incoming request and the outgoing response.
The golden rule is simple: Middleware registered earlier runs earlier on the request and later on the response.
Common Middleware Ordering Pitfalls
Let’s look at some classic mistakes. We’ll use the modern minimal API style with top-level statements for our examples, but the principles are identical for applications using a Startup.cs class.
Pitfall 1: Authentication Before Identity (Auth vs. Auth)
This is arguably the most common mistake. You want to check if a user is authorized to access a resource, but you forget to first figure out who the user is.
Incorrect Order:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// ... other middleware
app.UseAuthorization(); // Checks permissions
app.UseAuthentication(); // Identifies the user (e.g., from a JWT)
app.MapGet("/secure", () => "Secret data").RequireAuthorization();
app.Run();
Why it breaks: The UseAuthorization middleware runs and asks, “Does this user have permission?” The problem is, UseAuthentication hasn’t run yet, so the application has no idea who the user is. The HttpContext.User principal is not yet populated. As a result, authorization checks will always fail, and your users will get a 401 Unauthorized response even with a valid token.
Correct Order:
// ...
app.UseAuthentication(); // First, identify the user
app.UseAuthorization(); // Then, check their permissions
// ...
Always authenticate before you authorize.
From the Trenches: I once spent an entire afternoon debugging why a perfectly valid JWT was resulting in a
401. The API worked fine locally but failed in our staging environment. It turned out a recent merge had accidentally swapped these two lines. It’s a simple mistake, but its impact is significant.
Pitfall 2: Exception Handling Too Late
You want to catch any unhandled exceptions in your application and return a clean, user-friendly error response. To do this, you use exception handling middleware.
Incorrect Order:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouting();
app.MapGet("/bug", () => {
throw new InvalidOperationException("This is a bug!");
});
// Placed too late!
app.UseExceptionHandler("/error");
app.Run();
Why it breaks: The exception handling middleware can only catch exceptions thrown by middleware registered after it. In the example above, if the routing middleware or the endpoint itself throws an exception, UseExceptionHandler will never see it. The client will receive a raw, unhandled exception response (like an HTTP 500 with a stack trace in development), which is not what you want in production.
Correct Order:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Place at the very top to catch everything that follows
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseRouting();
app.MapGet("/bug", () => {
throw new InvalidOperationException("This is a bug!");
});
app.Run();
Place exception handling and developer exception pages at the very beginning of the pipeline to ensure they act as a global catch-all.
Pitfall 3: CORS Middleware in the Wrong Place
Cross-Origin Resource Sharing (CORS) is a security mechanism that controls which other domains can make requests to your API. The CORS middleware (UseCors) works by adding specific headers to the outgoing response.
Incorrect Order:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options => /* ... */);
var app = builder.Build();
// ...
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("MyPolicy"); // Too late!
app.MapGet("/", () => "Hello from API").RequireCors("MyPolicy");
app.Run();
Why it breaks: CORS needs to be evaluated very early in the pipeline. If an authentication or authorization middleware short-circuits the request (for example, by returning a 401 Unauthorized), the UseCors middleware might never run. This means the browser won’t receive the necessary Access-Control-Allow-Origin headers, and it will block the response, leading to a cryptic CORS error in the browser console.
Correct Order:
// ...
app.UseRouting(); // Routing needs to run first
app.UseCors("MyPolicy"); // Place CORS immediately after routing
app.UseAuthentication();
app.UseAuthorization();
// ...
The official Microsoft documentation recommends placing UseCors right after UseRouting but before UseAuthentication and UseAuthorization. This ensures the CORS policy is applied before any auth-related decisions short-circuit the pipeline.
Pitfall 4: Static Files After Routing
If your application serves static files (like a frontend for a Blazor WASM or SPA app), you want to serve them as efficiently as possible.
Incorrect Order:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouting();
app.UseAuthorization();
// Checks for static files after trying to route and authorize
app.UseStaticFiles();
app.MapGet("/api/data", () => "Some data");
app.Run();
Why it breaks: This isn’t a “hard break” but a performance pitfall. A request for /index.html or /css/site.css will first go through the routing and authorization middleware before UseStaticFiles gets a chance to handle it. This is wasted processing. The static file middleware is smart enough to short-circuit the pipeline if it finds a matching file, so it should be given that chance early.
Correct Order:
var builder = WebAplication.CreateBuilder(args);
var app = builder.Build();
// Serve static files first for efficiency
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapGet("/api/data", () => "Some data");
app.Run();
A Recommended Order for Success
While every application is different, a common and reliable middleware order in Program.cs looks something like this:
var builder = WebApplication.CreateBuilder(args);
// 1. Service Configuration (Dependency Injection)
// ... builder.Services.Add...
var app = builder.Build();
// 2. Configure the HTTP request pipeline.
// First: Exception Handling
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts(); // Enforce HTTPS in production
}
// Second: Security and HTTPS
app.UseHttpsRedirection();
// Third: Static files (if any)
app.UseStaticFiles();
// Fourth: Routing
app.UseRouting();
// Fifth: CORS
app.UseCors("YourPolicyName");
// Sixth: Authentication and Authorization
app.UseAuthentication();
app.UseAuthorization();
// Seventh: Endpoint Execution
app.MapControllers(); // For MVC/Web API controllers
app.MapGet("/", () => "Hello!"); // For minimal APIs
// Finally: Run the application
app.Run();
Expert Tip: Not all middleware uses the
Use...pattern. For example,UseRoutingandUseEndpoints(orMap...in minimal APIs) are special.UseRoutingdecides which endpoint will handle the request, andUseEndpointsexecutes it. Any middleware that needs to know about the matched endpoint, likeUseAuthorization, must go between them.
Final Thoughts
The ASP.NET Core request pipeline is a chain where the strength of the whole depends on the correct ordering of its links. A single misplaced line of code can introduce subtle bugs or gaping security holes. By understanding the purpose of each middleware component and following a logical sequence, you can build robust, secure, and predictable APIs. Always start with exception handling, follow with routing and security, and end with your endpoints. Getting this right from the start will save you countless hours of debugging down the road.