Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC authorization inside MapWhen() is applied to all controllers

In ASP.Net Core 3.0 Preview 7, I tried to write some code as follows:

public void Configure(IApplicationBuilder app) {
    app.MapWhen(context =>
        context.Request.Path.StartsWithSegments(
              new PathString("/UnsecureLog")),
        a => {
            a.UseRouting();
            a.UseEndpoints(endpoints => {
                endpoints.MapControllers();
            });
        }
    );
    
    app.UseAuthentication();
    app.UseAuthorization();

    app.MapWhen(context =>
        context.Request.Path.StartsWithSegments(
           new PathString("/SecureLog")),
        a => {
            a.UseRouting();
            a.UseEndpoints(endpoints => {
                endpoints.MapControllers()
                    .RequireAuthorization("MustBeReader");
            });
        }
    );
}

My goal was to allow certain controllers to be handled in the middleware without authentication, and my thinking was that MapWhen() was the way to pull that off.

Instead I see this error when hitting the /UnsecureLog endpoint:

System.InvalidOperationException: Endpoint ... contains authorization metadata,
but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization()
inside the call to Configure(..) in the application startup code.

Translation: "How's about you go and implement security features for that endpoint you didn't want to secure".

My takeaway is that any call to RequireAuthorization("MustBeReader") in any MapWhen() block handling controller logic will in fact be be applied to all MVC controller routes.

My current workaround is to remove the .RequireAuthorization("MustBeReader") call in the 2nd MapWhen() code block and re-apply it as an attribute ([RequireAuthorization("MustBeReader")]) to those endpoints I wish to secure. This works with no errors and produces the desired behavior.

But that sorta wasn't the goal, was it?

I'd prefer to manage whole groups of controllers with similar policies, while sparing others from security at all, and handle all of this from within Configure(). Instead I must apply the desired authorization requirements to each and every controller, piecemeal.

I am hoping there is a better way to implement routing that avoids the issue noted here. Maybe I'm doing something improperly.

Thoughts, anyone?

like image 310
Wellspring Avatar asked Aug 07 '19 14:08

Wellspring


People also ask

What is use of UseRouting and UseEndpoints in startup configure method?

UseRouting adds route matching to the middleware pipeline. This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request. UseEndpoints adds endpoint execution to the middleware pipeline. It runs the delegate associated with the selected endpoint.

What is IApplicationBuilder in .NET core?

UseExceptionHandler(IApplicationBuilder) Adds a middleware to the pipeline that will catch exceptions, log them, and re-execute the request in an alternate pipeline. The request will not be re-executed if the response has already started.

What would you use to add convention based routes to an ASP.NET Core application?

In order to use convention-based routing, we must do two things in our Startup. cs class. The AddControllersWithViews() method is new in ASP.NET Core 3.0, and adds MVC controller and views to the service layer so that we might use Dependency Injection (DI) for them.

What is middleware in ASP NET MVC?

Middleware is software that's assembled into an app pipeline to handle requests and responses. Each component: Chooses whether to pass the request to the next component in the pipeline. Can perform work before and after the next component in the pipeline.


3 Answers

It's important to note that app.UseAuthorization must appear between app.UseRouting() and app.UseEndpoints(...);

So, it should look something like this:

        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });
like image 98
mwilson Avatar answered Oct 17 '22 04:10

mwilson


Move the below code higher up in your Startup class (above app.MapWhen).

 app.UseAuthentication();
 app.UseAuthorization();
like image 39
Oyebode Avatar answered Oct 17 '22 05:10

Oyebode


I'm giving myself credit for the full answer, having spent some time today on this and posting the code. Thanks to mwilson for giving me the nudge to try again.

Prior to my new approach, every time the code ran, this warning was shown...

Startup.cs(189,13): warning ASP0001: The call to UseAuthorization
should appear between app.UseRouting() and app.UseEndpoints(..)
for authorization to be correctly evaluated.

So the solution is not merely recognizing that warning and heeding it, but also to find a way to appease the compiler gods. (And you'll see below that I still haven't figured out how to do that, yet.)

Here's what I did figure out today. I placed the UseAuthentication and UseAuthorization calls in two different places. This works. Somewhat.

In this API project I now am able to run anonymous unsecured endpoints, secured endpoints, and, for bonus points, secured GraphQL endpoints as well.

FWIW, code below:

        public void Configure(...)
        {
            ...
            ...
            app.UseStaticFiles();
            app.UseRouting();

            app.MapWhen(
                context => (context.Request.Path
                    .StartsWithSegments(new PathString("/api"))
                ),
                a =>
                {
                    a.UseRouting();
                    a.UseAuthentication();
                    a.UseAuthorization();
                    a.UseEndpoints(endpoints =>
                    {
                        endpoints
                            .MapControllers()
                            .RequireAuthorization(...);
                    });
                }
            );

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

            app.UseWebSockets();
            app.UseGraphQLWebSockets<...>(...);
            app.UseGraphQL<...>(...);
        }

That works, but I still get the compiler warning. What's more, if I get too smart by half and try to use the following controller...


    [Route("vapi/[controller]")]
    //[AllowAnonymous]
    public class VersionController : Controller
    { ...

along with this additional Startup.cs code...

            app.MapWhen(
                context => (context.Request.Path
                    .StartsWithSegments(new PathString("/vapi"))
                ),
                a =>
                {
                    a.UseRouting();
                    // a.UseAuthentication(); <-- look, Ma, no authentication!
                    // a.UseAuthorization(); <-- look, Ma, no authorization!
                    a.UseEndpoints(endpoints =>
                    {
                        endpoints
                            .MapControllers()
                            .RequireAuthorization(...);
                    });
                }
            );

I also still get the error noted in my OP. (It's all coming back to me, like an old nightmare...)

Endpoint ....Controllers.VersionController.Version (...) contains authorization
metadata, but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization() ...

So where I am leaving the matter is this: I still cannot do what I wanted to do, which was to secure only certain controller paths. What I can do (without adding that "vapi"-path-supporting code to Startup) is this:

    [Route("api/[controller]")]
    [AllowAnonymous]
    public class VersionController : Controller
    { ...

So... I'll call this the answer, until someone posts a usable set of code that improves on it.

like image 3
Wellspring Avatar answered Oct 17 '22 06:10

Wellspring