Search code examples
c#asp.netasp.net-coreroutesmiddleware

Custom middleware (or authorize) for specific route in ASP .NET Core 3.1 MVC


In my ASP .NET Core 3.1 MVC app, I use endpoint routing like so

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

            endpoints.MapControllerRoute(
                name: "access",
                pattern: "access/",
                defaults: new { controller = "Home", action = "Access" });
        });

So browsing to /access, launches the Access action, where the app checks if user complies with some access requirements.

if (access checks...)
{
    return View();
}

Now I would prefer having this check in a custom middleware (or possibly a custom authorize attribute) instead of having it in the Controller. So my question to you is, how should I rewrite the UseEndPoints call, to include a custom middleware for the /access area?


Solution

  • Authorization policy extending [Authorize]

    You could do this using authorization policies. Configure these in your Startup.cs inside ConfigureServices(IServiceCollection services) like so:

    services.AddAuthorization(options =>
    {
        // Create your own policy and make the "access checks" in there
        options.AddPolicy("MyAccessPolicy", policy => policy.RequireAssertion(httpCtx =>
        {
            if (access checks...)
                return true;
            else
                return false;
        }));
    });
    

    Then you simply decorate your controller action with the Authorize attribute like so:

    [Authorize(Policy = "MyAccessPolicy")]
    public IActionResult Access()
    {
        return View();
    }
    

    Now, whenever you try to go to /access this policy will run, and if the policy returns false, the user will be met with an HTTP 403 (Forbidden) status code.

    Custom middleware mapped to route

    In response to your comment here's an example of a middleware and how to map it to a specific route.

    An example from my own project with a global error handling middleware (some irrelevant parts stripped out):

    public class ExceptionHandlingMiddleware : IMiddleware
    {
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            try
            {
                // Call next middleware
                await next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }
    
        private async Task HandleExceptionAsync(HttpContext context, Exception ex)
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            ErrorDetails error = null;
            if (ex is FileNotFoundException || ex is DirectoryNotFoundException)
            {
                context.Response.StatusCode = StatusCodes.Status404NotFound;
                error = _localizer.FilesOrFoldersNotFound();
            }
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(JsonConvert.SerializeObject(
                new CustomResponse(false, error ?? _localizer.DefaultError()),
                _serializerSettings));
            }
        }
    

    To only use this middleware for specific routes you could do as suggested here:

    // Startup.cs
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.Map("path/where/error/could/happen",
            b => b.UseMiddleware<ExceptionHandlingMiddleware>());
        // ...
    }
    

    Or check the path inside the middleware itself:

    // ExceptionHandlingMiddleware.cs
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (!context.Request.Path.StartsWithSegments("path/where/error/could/happen"))
        {
            // Skip doing anything in this middleware and continue as usual
            await next(context);
            return;
        }
    
        // Use middleware logic 
        try
        {
            // Call next middleware
            await next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }