8
votes

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?

3

3 Answers

11
votes

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);
    }
}
4
votes

You can Extending AuthorizeAttribute along with IAuthorizationFilter in Asp.Net Core

1.Create a class which extends AuthorizeAttribute, this will used on top of controller or action like Asp.Net core’s inbuilt [Authorize] attribute.

2.Implement the method OnAuthorization(AuthorizationFilterContext context) which is part of IAuthorizationFilter interface.

3.Call return keyword without any additional operation for authorized user.

4.Set AuthorizationFilterContext result as Unauthorized for unauthorized users as context.Result = new UnauthorizedResult()

    public class SampleAuthorizePermission : AuthorizeAttribute, IAuthorizationFilter
{
    public string Permissions { get; set; }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (string.IsNullOrEmpty(Permissions))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        var userName = context.HttpContext.User.Identity.Name;

        var assignedPermissionsForUser =
            MockData.UserPermissions
                .Where(x => x.Key == userName)
                .Select(x => x.Value).ToList();

        var requiredPermissions = Permissions.Split(",");
        foreach (var x in requiredPermissions)
        {
            if (assignedPermissionsForUser.Contains(x))
                return;
        }

        context.Result = new UnauthorizedResult();
        return;
    }
}

and in your controller

[SampleAuthorizePermission(Permissions = "CanRead")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }
0
votes

Taking the middleware specific approach in .NET Core 3.1, we can conditionally add middleware using the following- In configure method-

app.UseWhen(context=>context.Request.Path.StartsWithSegments("your-route-url"),branch=>branch.useMiddleware(););

There are a few ways how the pipeline branching can happen, follow the docs for more information- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0#branch-the-middleware-pipeline