Custom route constraint causes intermittent 404 errors

168 Views Asked by At

I have an Asp.Net Core 1 RC1 application that uses a custom route constraint to control access to the application. The application (hosted on a server running IIS 7.5) is getting intermittent 404 errors which I suspect is caused by this routing constraint. Here you can see a screenshot that shows the intermittent 404 errors:

enter image description here

I suspect that this issue is related to the code that defines the route constraint not being thread-safe. The custom route constraint needs a DbContext because it needs to check in the database if the application is enabled for the brand specified in the route, and I suspect that this DbContext instance could be causing the issue. Here is how the routing is defined in the application:

// Add MVC to the request pipeline.
var appDbContext = app.ApplicationServices.GetRequiredService<AppDbContext>();
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "branding",
        template: "branding/{brand}/{controller}/{action}/{id?}",
        defaults: new { controller="Home", action="Index" },
        constraints: new { brand = new BrandingRouteConstraint(appDbContext) });
});

And here is the custom route constraint:

// Custom route constraint
public class BrandingRouteConstraint : IRouteConstraint
{
    AppDbContext _appDbContext;
    public BrandingRouteConstraint(AppDbContext appDbContext) : base() {
        _appDbContext = appDbContext;
    }
    public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
    {
        if (values.Keys.Contains(routeKey))
        {
            var whiteLabel = _appDbContext.WhiteLabels.Where(w => w.Url == values[routeKey].ToString()).FirstOrDefault();
            if (whiteLabel != null && whiteLabel.EnableApplication != null && (bool)whiteLabel.EnableApplication)
            {
                return true;
            }
        }
        return false;
    }
}

Can anyone confirm that this issue is caused by the code not being thread-safe and recommend a way to change the implementation so that it is thread-safe?

1

There are 1 best solutions below

1
On

I can't comment on RouteContraint's, haven't used them much, but have you tried Resource Based Authorization instead? Looks like it might be more suited to what you're trying to achieve?

From here and here:

Request authentication service inside your controller

public class DocumentController : Controller
{
    IAuthorizationService authorizationService;

    public DocumentController(IAuthorizationService authorizationService)
    {
        this.authorizationService = authorizationService;
    }
}

Apply authorization checks in your Action:

public async Task<IActionResult> Edit(Guid documentId)
{
    Document document = documentRepository.Find(documentId);

    if (document == null)
    {
        return new HttpNotFoundResult();
    }

    if (await authorizationService.AuthorizeAsync(User, document, Operations.Edit))
    {
        return View(document);
    }
    else
    {
        return new HttpUnauthorizedResult();
    }
}

I've used the OperationAuthorizationRequirement class in the sample, so define this class in your project:

public static class Operations
{
    public static OperationAuthorizationRequirement Create =
        new OperationAuthorizationRequirement { Name = "Create" };
    public static OperationAuthorizationRequirement Read =
        new OperationAuthorizationRequirement { Name = "Read" };
    public static OperationAuthorizationRequirement Update =
        new OperationAuthorizationRequirement { Name = "Update" };
    public static OperationAuthorizationRequirement Delete =
        new OperationAuthorizationRequirement { Name = "Delete" };
}

Implement the authorization handler (using built in OperationAuthorizationRequirement requirement):

public class DocumentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
    protected override void Handle(AuthorizationContext context,
                                   OperationAuthorizationRequirement requirement,
                                   Document resource)
    {
        // Validate the requirement against the resource and identity.
        // Sample just checks "Name"field, put your real logic here :)
        if (resource.Name == "Doc1")
            context.Succeed(requirement);
        else
            context.Fail();
    }
}

And not forgetting ConfigureServices:

services.AddInstance<IAuthorizationHandler>(
    new DocumentAuthorizationHandler());

It's a bit more work, but adds quite a lot of flexibility.