Why does `UseAuthentication` have to be placed after `UseRouting` and not before?
Asked Answered
T

4

16

According to the documentation, the order of middleware should be like this:

app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

I have middleware to protect static files, based on this article (protecting certain routes). The problem I encounter is that the order doesn't work for me. I can only protect a folder if the user is already authorized. So I need to place UseProtectFolder before UseStaticFiles and after UseAuthentication and UseAuthorization:

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseProtectFolder(new ProtectFolderOptions
{
    Path = "/Secret",
    PolicyName = "Authenticated"
});
app.UseStaticFiles();

But this will not return any static file. It looks like UseRouting is doing something that makes the file unavailable, returns 404, because when I change the order to this, moved UseRouting after UseStaticFiles, it works:

app.UseAuthentication();
app.UseAuthorization();

app.UseProtectFolder(new ProtectFolderOptions
{
    Path = "/Secret",
    PolicyName = "Authenticated"
});
app.UseStaticFiles();

app.UseRouting();

So the actual change in order is that UseAuthentication is placed before UseRouting (and even before UseStaticFiles).

From the documentation:

The order that middleware components are added in the Startup.Configure method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.

My question is now: in the order as documented, why is UseAuthentication placed after UseRouting?

Is there a particular reason or is it for performance reasons only? And by moving the authentication/authorization earlier in the pipeline, does this affect the response (reverse order)?

Tomasz answered 20/10, 2019 at 17:41 Comment(0)
T
16

After posting this question I've opened an issue about routing on github, and one about localization, hoping for more information. And though not everything was answered in a direct way, it helped me to find an answer to this question.

After reading the comment of David Fowler:

UseAuthorization() -> Will look at the populated user and the current endpoint to determine if an authorization policy needs to be applied.

it occured to me that there is no problem with UseAuthorization. It's meant for endpoints, so I don't need it to protect the folders. It also explains why this statement only makes sense after the UseEndpoints statement.

For a complete picture of my configuration, I have one policy provider (includes policies), one url rewriter (like UseDefaultFiles) and middleware that protects certain folders.

My conclusion is that I can use the following order, which is almost the same as documented:

// Identify the user. The only statement that is not in the order as documented
app.UseAuthentication();

// Middleware that adds policies
app.UsePolicyProvider();
// Protect the folder by policy
app.UseProtectFolder(new ProtectFolderOptions { Path = "/p", PolicyName = "admin" });
// URL rewriter for serving tenant specific files
app.UseTenantStaticFiles();

// Serve the static files
app.UseStaticFiles();

app.UseCookiePolicy();
app.UseCors();

app.UseRouting();
app.UseRequestLocalization();
app.UseAuthorization();
app.UseEndpoints();

Two remarks about the order:

  1. UseRequestLocalization only works after UseRouting
  2. UseStaticFiles doesn't work after UseRouting when a URL rewriter, like UseDefaultFiles, is involved.
Tomasz answered 27/10, 2019 at 19:4 Comment(3)
if you still happen to have them handy, would you mind posting links to those issues you opened on github in the comments here? I'd like to see more of those discussions.Heterodoxy
Thanks for the links! I was still curious about what was really going on under the hood so I did some more digging and added another answer about what UseRouting is making available for UseAuthentication and UseAuthorization.Heterodoxy
Please update your answer, according to learn.microsoft.com/en-us/aspnet/core/security/… UseCors must be placed after UseRoutingAmen
H
12

One thing I wanted to add to @Ruard's answer regarding the "Why UseRouting before UseAuth" part of this question is this excerpt from Overview of ASP.NET Core authentication:

When using endpoint routing, the call to UseAuthentication must go:

  • After UseRouting, so that route information is available for authentication decisions.
  • Before UseEndpoints, so that users are authenticated before accessing the endpoints.

I was still curious what route information was necessary before UseAuthentication() could be called, so I did some digging into the source code and discovered that the info UseRouting() has to make available for both UseAuthentication() and UseAuthorization() is just the Endpoint class. Specifically, Endpoint.Metadata which is of type EndpointMetadataCollection.

EndpointMetadataCollection is just an object array, so to figure out what might actually be populated there, I just created an empty WebAPI project, set an authorization attribute above a controller, threw in some test middleware, and added a breakpoint right after assigning HttpContext.GetEndpoint().Metadata to a variable.

Turns out, one of the things it's populating is data about the Authorization attribute I added:

EndpointMetadataCollection variable values in debug

In hindsight, that makes a lot of sense. It would be dumb to try to figure out if a request was authorized before you even knew if the endpoint required authorization (or if a user was authenticated before we knew the request required authentication).

Something else I stumbled upon that was really insightful was this article by Areg Sarkissian that really gets into the nitty gritty of endpoint routing without being as dry as microsoft docs. This example in particular did an excellent job of showing what I mentioned above:

{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
        app.UseHsts();

    app.UseHttpsRedirection();

    app.UseRouting(routes =>
    {
        routes.MapControllers();

        //Mapped route that gets attached authorization metadata using the RequireAuthorization extension method.
        //This metadata will be added to the resolved endpoint for this route by the endpoint resolver
        //The app.UseAuthorization() middleware later in the pipeline will get the resolved endpoint
        //for the /secret route and use the authorization metadata attached to the endpoint
        routes.MapGet("/secret", context =>
        {
            return context.Response.WriteAsync("secret");
        }).RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin" });
    });

    app.UseAuthentication();

    //the Authorization middleware check the resolved endpoint object
    //to see if it requires authorization. If it does as in the case of
    //the "/secret" route, then it will authorize the route, if it the user is in the admin role
    app.UseAuthorization();

    //the framework implicitly dispatches the endpoint at the end of the pipeline.
}
Heterodoxy answered 4/2, 2020 at 6:15 Comment(1)
@Jerrect how do you make AuthenticationMiddleware bypass requests that don't need authorization?Kellykellyann
A
0

app.UseAddAuthentication() is a cross-cutting concern that occurs after routing is applied. Not all routes will have authentication applied to them.

Here's a suggested ordering:

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
     app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for 
 production scenarios, see https://aka.ms/aspnetcore-hsts.
     app.UseHsts();
}

 app.UseHttpsRedirection();
 app.UseStaticFiles();
 app.UseCookiePolicy();

 app.UseRouting();
 app.UseRequestLocalization();
 app.UseCors();

 app.UseAuthentication();
 app.UseAuthorization();
 app.UseSession();
 app.UseResponseCompression();
 app.UseResponseCaching();

 app.MapRazorPages();
 app.MapControllerRoute(
      name: "default",
     pattern: "{controller=Home}/{action=Index}/{id?}");

 app.Run();
Aeonian answered 1/8, 2022 at 3:30 Comment(0)
W
0

What is an Authentication? Authentication is the process or action of verifying the identity of a user or process.

So the AuthenticationMiddleware i.e. app.UseAuthentication(); step should only identify the user and that should be all its responsibility. I can't imagine a process when we need to know endpoint information for the user authentication. In most cases, endpoint information is required by the authorization process.

For example, in the split tenant applications, we should know the user (actually its tenant) before routing because different tenants can have different features enabled, and, as a result, different endpoints available.

So, in my opinion, only app.UseAuthorization() should go after the routing. Authentication middleware should go before it, as early as it possible.

Waxbill answered 8/6, 2023 at 16:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.