Preventing access to folders using RouteExistingFiles property

RouteExistingFiles Property

When a user request for a static resource like an image, video etc. that is located in a particular folder the ASP.NET happily serves that resource to the user unless we have set some restrictions. Sometimes we need to protect these folders from delivering these resources to users other than the owner. In simple cases we can prevent this through web.config settings but in complex cases like it would be nice if we could control the accessibility through an action/filter and for that we have to direct those requests through MVC pipeline and there comes the RouteExistingFiles property. By setting this property to true we can say MVC to handle those requests instead of giving that responsibility to IIS.

In this article we will see how we can utilize the RouteExistingFiles property with an authorization filter to prevent users from accessing unauthorized resources.

Example

Let say we have a folder that contains private photos of users. We don't want anyone to access the images other than the owner.

Here is our Global.asax.cs.

public static void RegisterRoutes(RouteCollection routes)
{
	routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

	routes.MapRoute(
		"Default",
		"{controller}/{action}/{id}",
		new { controller = "Home", action = "Index", id = UrlParameter.Optional }
	);
}

Let's start by setting the RouteExistingFiles property to true to stop IIS handling those requests and delivering the images directly.

public static void RegisterRoutes(RouteCollection routes)
{
	routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

	routes.RouteExistingFiles = true;
	...
}

Next thing is, we have to create a route to handle those requests. If you see the URL pattern it resembles the physical path of the resource but that don't need to be the case.

routes.MapRoute(
	"photo",
	"premium/photos/{accountNo}/{image}",
	new { controller = "Photo", action = "Index" }
);

As you guessed we should create a controller and action followingly. The Index action returns the image getting the complete path from the request url.

public class PhotoController : Controller
{
	public FileResult Index()
	{
		return File(Request.RawUrl, "image/jpeg");
	}
}

Still our action is not safe we have to decorate with a custom authorize attribute. What our custom authorize attribute does is it checks the accountNo passed in the url with the one stored in the user's session and if they don't matches returns a 401.

public class PhotoAuthorizeAttribute : AuthorizeAttribute
{
	protected override bool AuthorizeCore(HttpContextBase httpContext)
	{
		if (base.AuthorizeCore(httpContext))
		{
			var accountNo = httpContext.Request.RequestContext.RouteData.Values["accountNo"];

			if (accountNo != null && httpContext.Session["AccountNo"].ToString() == accountNo.ToString())
			{
				return true;
			}

			return false;
		}

		return false;
	}
}

We have to decorate our action that returns images with PhotoAuthorize attribute.

[PhotoAuthorize]
public FileResult Index()
{
	return File(Request.RawUrl, "image/jpeg");
}

There is one important problem we notice at-once after setting the RouteExistingFiles to true. The styles and javascript files from the Content folder are blocked. That is a problem! this property acts kind of global once we set then it expects all the requests to resources to be handled through the routing module. So how we can deliver the css, js? it's bad to create controllers and actions for them, fortunately we could use the IgnoreRoute method for the rescue.

public static void RegisterRoutes(RouteCollection routes)
{
	routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

	routes.IgnoreRoute("Content/{*relpath}");

	routes.RouteExistingFiles = true;

	routes.MapRoute(
		"photo",
		"premium/photos/{accountNo}/{image}",
		new { controller = "Photo", action = "Index" }
	);

	routes.MapRoute(
		"Default",
		"{controller}/{action}/{id}",
		new { controller = "Home", action = "Index", id = UrlParameter.Optional }
	);
}

The IgnoreRoute says the routing module don't handle those requests and note that we have to call that method before setting the RouteExistingFiles.

Avoid caching images by browser

The final problem is browser cache. The browser will cache the images and still an unauthorized user can see the cached version. As a precautionary we have to request the browser don't cache those images and that can be easily achieved using the OutputCache attribute.

[PhotoAuthorize]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public FileResult Index()
{
	return File(Request.RawUrl, "image/jpeg");
}

Summary

So we have seen how we can protect sensitive resources from anonymous/unauthorized users with the help of RouteExistingFiles property and an authorization filter. Also, when setting the property to true we should use the IgnoreRoute method to avoid handling the requests for static resources like css, js by MVC unless there is a need.

blog comments powered by Disqus