views:

145

answers:

2

Hello. I have an ASP.NET MVC application for which I store uploaded content files in a virtual directory. This virtual directory is directly underneath my MVC website in IIS.

My problem is that the virtual directory allows anonymous access. Anyone, logged in or not, can type in a public URL to my virtual directory and read the files in it. Is it possible to configure IIS (or something else) in a way that forces any requests to this virtual directory to run an authentication/authorization routine before allowing access?

Is this something I can configure in my website's web.config, or does the request never hit any server side code in this case? If it never hits server side code (and feeds the request directly to IIS), how can I change my implementation to require my site to authenticate/authorize and then serve my file.

Thanks for your help!

A: 

If you are running IIS 7 or better, with integrated authentication, then the uploaded content will be running through the ASP.NET runtime so all the normal authentication tricks work--just add a web.config to the folder to enforce some security.

IIS 6 or earlier presents different challenges. A better idea is to "front" the files with a HTTP handler that grabs them out of a folder so that your files are protected by the runtime.

Wyatt Barnett
Thanks. I am running IIS 7 and I added this section to <settings.web>:<authorization> <deny users="?"/> <allow users="*"/></authorization>The result is, I lose my CSS on the login page before authentication (which is a good sign), however I still am able to access the content files within the VD at the same root as the web.config. Any ideas?
kmehta
VD is a separate app, so it needs its own web.config. Haven't tried this scenario, so I'm not sure if it will pick up your authentication.
Wyatt Barnett
+1  A: 

I don't know that this is possible exactly how you requested it. But, I do know of a way you can do this using a different method. Maybe it will work for you.

The idea is to store the secured files in a folder that is not available from the web (not a virtual directory). Then, have a method on a controller like Controllers/DownloadController.cs that handles user authentication and file serving. Here's a sample method that can retrieve a file from c:\myfiles:

Controllers/DownloadController.cs (action method only):

[Authorize]
public FileResult Download(string filename)
{
    //get content type from file extension
    var contentType = getContentTypeFromExtension(filename);

    //return file with filename as third argument to 
    //   trigger browser's download bahavior
    return File(Path.Combine(fileFolder, filename), contentType, filename);
}

[Authorize]
public FileResult Open(string filename)
{
    //get content type from file extension
    var contentType = getContentTypeFromExtension(filename);

    //return file without download filename so that 
    //   the file is opened in browser (if possible)
    return File(Path.Combine(fileFolder, filename), contentType);
}

//method to get content type of file from registry using file extension
static string getContentTypeFromExtension (string fileName)
{
    string contentType = "application/unknown";
    string ext = System.IO.Path.GetExtension(fileName).ToLower();
    Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
    if (regKey != null && regKey.GetValue("Content Type") != null)
        contentType = regKey.GetValue("Content Type").ToString();
    return contentType;
}

fileFolder variable should be defined at the class level. I took it out because it was messing with the code formatting. :)

Byron Sommardahl
Interesting. I wanted to avoid using a handler, but this may be my only option. One question, when the "File" returned is an image, does it open in the browser just as if the user clicked an image or does it start a file download with the image as the download content. I'm hoping the former. Thanks.
kmehta
If you want it to behave like a download, just add a third argument to the return File(...) line. I will update the code to give an example.
Byron Sommardahl
Thanks! I'll give this a shot.
kmehta
I had to make one tweak. The first parameter of the FileResult needed to be a FileStream in order for this to work. So mine looked like return File(new FileStream(Path.Combine(fileFolder, filename)), contentType);
kmehta