DotNetNuke Tips & Tricks

Friday, December 5, 2008 by Ian Robinson

Improving the Secured File Download UX for Unauthenticated Users

Filed under: Tips & Tricks

DotNetNuke has extensive security features. One of which is enforcing role based permissions when accessing files. The general workflow is to say that "Registered Users" get to see a particular file, you then create a link to that file using DNN, and as a matter of course, the roles are enforced. This uses what's known as a file ticket to request the file, so that it is not linked to directly.

This is great if you are only providing those links to currently authenticated users. However, if you want to provide these links to unauthenticated users, you get a pretty unfriendly experience. Granted, this is an edge use-case, and an argument could be made against doing this in the first place, but every project is different, and this just might make sense for you.

Background

There are a couple of ways to get yourself into this situation - the one I set up was the following:

  • Drop a links module on the page,
  • add a new link to a file
  • Go to the file manager, and make that file's folder available only to registered users.
  • Make sure the module is visible to all users

Currently, in DNN 4.9, you get a blank white page (even if you view source) with the following content:

You do not have permission to view this file.

Getting better

We could easily make that better by localizing it with a link to the login page. This improves the user experience a little bit. But really, we can put anything we want in here. Including a full HTML page with web site branding to give the user a more pleasant experience.

The value to localize can be found in GlobalResources\SharedResources.resx under the "FilePermission.Error" key. Note that any time you make a change to this file, the app restarts. So try to be patient when making changes.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>My Temporary Page</title> 
</head> 
<body> 
<p>Dear user, please <a href="http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx">Login</a> to view the file you have requested. Thanks, Admin.</p> 
</body> 
</html>

Actually useful?

To take it a step further, we could add a timed redirect to the page, using a meta refresh. This will give the user the ability to see a custom informational page, as well as automatically be redirected.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>My Temporary Page</title> 
<meta http-equiv="REFRESH" content="10;url=http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx"  /> 
</head> 
<body> 
<p>Dear user, please <a href="http://localhost/dotnetnuke_2/Home/tabid/36/ctl/Login/Default.aspx">Login</a> to view the file you have requested. You will be redirected in 10 seconds. Thanks, Admin.</p> 
</body> 
</html>

Probably a lot better now...

Now, if we wanted to get really fancy, we could ditch the meta refresh, and enhance our HTML page to use JavaScript to redirect. The great benefit of this is that we can tack on a returnurl parameter to our redirect with the file ticket's URL. This will tap into the core login behavior and deliver the appropriate content after the user has logged in.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>My Temporary Page</title> 
<script type="text/javascript"> 
<!-- 
function redirectToLogin() { 
var l = document.location.href; 
if (l.toLowerCase().indexOf("fileticket") != -1) { 
var virtualDirectory = ""; 
var loginPage = "/Default.aspx?ctl=login&returnurl=";  
var returnUrl = encodeURIComponent(l.substr(l.lastIndexOf("/"))) 
window.location = "/" + virtualDirectory + loginPage + encodeURIComponent("/") + virtualDirectory + returnUrl; 
} 
} 
--> 
</script> 
</head> 
<body onload="setTimeout('redirectToLogin()', 5000)"> 
<p>Header with site branding, navigation</p> 
<p><a href="javascript:redirectToLogin();">Dear user, go to the login page!</a></p> 
<p>Footer with site branding, navigation?</p> 
</body> 
</html>

There are a few things that you may want to modify above for your implementation:

  • timeout (on body onload)- this is how fast your page will redirect to the login page/control. If you don't want a "landing page", set this to 0.
  • virtualDirectory - if you are using a virtual directory (e.g. http://localhost/dotnetnuke_2/), you'll want to set this to "dotnetnuke_2"
  • loginPage - relative link to the login page/control

Summary

When we want to provide a link for unauthenticated users to a file and enforce role security, we now have the ability to:

  • provide that user with more information when they click on the link
  • implement consistent branding on that informational page
  • automatically redirect them to the login screen
  • supply a return URL so that after they login, they are taken directly to the file.

As this is just a quick and dirty implementation, I'd love to see someone with a need for this actually make it awesome. (yes, that's right...I don't even have a project to implement this on...)

And as always, if you have a project to share with the community, you should submit it to dnnGallery!

A note about unwanted redirects

One catch with the meta refresh is that the DNN language editor shows you a preview of the content. So after you localize it with a meta refresh the first time, ever subsequent time you visit the SharedResources language file in the editor, the page will redirect.

I don't think the same is true of the JavaScript, but because different browsers could handle this differently, I went ahead and included a check so that the JavaScript redirect will only occur if the URL contains "FileTicket". Feel free to experiment.

Comments

Will Strohl
Friday, December 5, 2008 5:17 AM
Great walk through! I too am excited to see a more thorough implementation!
Mitchel Sellers
Friday, December 5, 2008 6:26 AM
What a great walkthrough, it is customizations like these that can really help sites go the extra mile and improve usability.

I'd also strongly recommend putting this in as a Gemini ticket to see about getting an improved default implementation.
Ian Robinson
Friday, December 5, 2008 11:08 AM
Thanks for the positive feedback guys!

Another note: There is a reason that we use a relative path for the return URL - DotNetNuke filters out any URL with a "://" in it (i.e. Any fully qualified URL). So that might be something helpful to keep in mind!
David Muscat
Friday, May 22, 2009 4:14 PM
Ian,

I changed the value of FilePermission.Error in SharedResources.resx file to your first suggestion (the simplest one). Then I clicked on a link to a file that is in a folder accessible to registered users only and ended up in a blank page.

Next, I just changed it back to the default (just plain text) and added a couple of words. The next time I clicked on the file link, I got the default message plus the couple of extra words.

Therefore it seems to me that the html text is not being interpreted correctly by DNN. Is there some option that I need to switch on. I'm using version 4.9.3.

thanks, David
David Muscat
Friday, May 22, 2009 4:31 PM
nevermind...I realized that I cannot use < and > ...shows that I'm new to this
blog comments powered by Disqus