The Goal:
You need to create a site that loads images based on a user's account information that can't be accessed without proper credentials, such as an asset manager that pulls a user's logos and photos and displays them as part of a larger page.
The Problem:
First, it has to be secure. Second, you want it to be AJAX driven.
What Not To Do:
Don't rely on simple obfuscation methods to keep people out. Also, preventing what most servers refer to as "page indexing" does not satisfy our needs.
It is very important to know that storing images in randomly generated folder names does not provide adequate security, even if you then randomize the contents' filenames, nor does configuring your web server to prevent viewing folder contents in a browser. Both of these methods are great to implement, but should not be your only means of security.
A Basic Understanding Of Web Servers:
If you already understand how web servers work, skip this section.
I am assuming you are using Apache, or something similar. I'm not very well versed in other web servers, but hopefully the concepts here are broad enough that they can easily be ported. An important note: when I say web server, I am referring to the piece of software used to deliver content to users over HTTP or HTTPS.
Web servers use configuration files to determine what files are accessible via the web (which means either the HTTP or HTTPS protocol). On Apache, that is provided with the DocumentRoot directive, as such:
DocumentRoot /var/www/mywebpage
If this server was set up to deliver files for the domain name thegrassnull.com then the web server would map files in the folder /var/www/mywebpage to the domain thegrassynull.com, so typing thegrassynull.com/index.html into your browser tells the web server to find the file called 'index.html' on the server's hard driver in the folder /var/www/mywebpage (if you are unfamiliar with this style of folder paths, this is what is used in systems such as Unix and Linux. A Microsoft Windows equivalent would be something like C:\www\mywebpage)
Here's the important thing to understand: If we put files in the /var/www/mywebpage folder, the can be accessed by a browser provided a person knows (OR GUESSES) the file or folder names. If, however, we put a file in the /var/www folder (so, one level up from where our web page is kept), the browser has no way to reach it. Under normal circumstances, the web server has no idea what is in that folder, and doesn't care.
The Groundwork:
1) Create a folder to store the actual images
So, let's say we create a new folder /var/www/secureContents
The web server doesn't inherently know what is in that folder, and browsers (ie, the users that connect to your site) have no way of accessing it. To push this data to our users' browsers, we need to write a server script that will act as our data broker and retrieve these files. An important thing to keep in mind is that the web server runs as a user, and that user will need permission to access all of the files we plan on retrieving. The implementation details of this depend on the operating system your web server is running on, and I won't go into that (unless a demand for that exists).
2) Create the database that will contain the user information
I will leave the details of the user database up to you, but the key elements are the following:
user name
password
securepath
fileList -- Optional (we'll get to this in our PHP code later)
Password should NEVER BE HUMAN READABLE TEXT. EVER. Depending on where you live, you may have access to functions such as SHA, or MD5. Using those, you can create a hash of your passwords to store in the database. Again, that is outside the scope of this tutorial, but information can easily be found using your favorite search engine.
Securepath in this case could be something like /var/www/secureContents/user1 The important thing is that the folder exists prior to your attempt to access data in it, and that it is not a folder within your DocumentRoot.
3) Create the server script
Here is the framework for the server code that we will be creating. We will add more to this as we go along. Let's call this file Services.php and let's save it in the folder /var/www/mywebpage/php
//This code will provide data geared towards AJAX delivery.
//depending on how you implemented your user database, this code may be different
//start by creating a function for authenticating users
function Authenticate($username, $password) {
$con = mysql_connect("localhost","databaseuser","databaseuserpassword");
//these variables depend on YOUR configuration
mysql_select_db("my_db", $con);
//my_db is the scheme (in MySql) that you are using to store the user information table
$result = mysql_query("SELECT secureFolder FROM Users where username = \"" . addslashes($username) . "\" AND password = \"" . md5($password) . "\"" );
//if using mysql, look into mysql_real_escape_string instead of addslashes in the above code
$numberOfResults = mysql_num_rows($result);
//$numberOfResults MUST BE EXACTLY 1 TO BE A CORRECT USERNAME AND PASSWORD PAIR.
if ($numberOfResults == 1) {
return true;
} else {
return false;
}
} //end Authenticate
if (isset($_GET['action'])) {
$action = $_GET['action']; //a little short-hand
} else { //no action specified. Return an error
header('HTTP/1.1 403 Forbidden');
}
if ($action == 'getListOfFiles') {
//This code is up to you. You can either iterate through the filesystem, or store this
//information in the user database
//File iteration, in the long run, will be more flexible, but will be harder to code. If more than
//one user share the same folder, that is if you put all user images in
///var/www/secureFolder then you can't iterate through the filesystem
//Assuming we have our file list, return it in json format, or whatever format you prefer.
//At the very list, we will need to get the folder path for the user
//IMPORTANT NOTE: How you handle authentication here is important. I don't use sessions,
//but that is a matter of personal preference. Instead, I authenticate each request.
if (Authenticate($_GET['username'], $_GET['password']) === true) {
//these variables depend on YOUR configuration
$con = mysql_connect("localhost","databaseuser","databaseuserpassword");
mysql_select_db("my_db", $con); //my_db is the scheme (in MySql) that you are using to store the user information table
$result = mysql_query("SELECT secureFolder FROM Users where username = \"" . addslashes($_GET['username']) . "\" LIMIT 1" );
//we could check password again, but calculating MD5 takes CPU...
while($row = mysql_fetch_assoc($result)) {
$filePath = $row['secureFolder'];
}
//use $filePath to get your $fileList. Again, this is up to you, but in this example I
//assume that fileList is an array of filenames
//use echo instead of return, for AJAX requests, to push them back to the browser
echo json_encode($fileList);
} else {
//the user didn't authenticate. Chose how you will handle this
echo 'NULL';
}
}
if ($action =='getFile') {
//This is the guts of our delivery system. There is a lot you can do here, but this code will show just how to retrieve PNGs from the server and return them.
//You should be checking file types and such, but for the purpose of this example we will just assume that everything comes to us in the format that we expect (which is not a safe assumption)
//Also, there is a lot of room for performance improvements here
if (Authenticate($_GET['username'], $_GET['password']) === true) {
$con = mysql_connect("localhost","databaseuser","databaseuserpassword"); //these variables depend on YOUR configuration
mysql_select_db("my_db", $con); //my_db is the scheme (in MySql) that you are using to store the user information table
$result = mysql_query("SELECT secureFolder FROM Users where username = \"" . addslashes($_GET['username']) . "\" LIMIT 1" ); //we could check password again, but calculating MD5 takes CPU...
while($row = mysql_fetch_assoc($result)) {
$filePath = $row['secureFolder'];
}
if ( file_exists($filePath . $_GET['fileName'])) {
$im = imagecreatefrompng($filePath . $_GET['fileName']);
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
}
}
}
?>
Note: If you don't need any image manipulation at all (such as converting from one format to another), then it is faster to use file_get_contents() instead of the GD commands.
4) Create the HTML code
I'll leave the guts of it up to you, but here is the bare minimum (and yes, page encode and doctype ARE BARE MINIMUM). We are also going to include Jquery for our AJAX functions, and another script for our code. I prefer a local copy of jquery, but most people recommend using an online version for caching reasons. The choice is yours.
<doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="js/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="js/image-loader.js"></script>
<head
<body>
<!-- create a space to place our dynamically loaded images --!>
<div id='image-area'> </div>
</body>
</html>
5) Create our javascript code
//we will have our AJAX fire when the DOM is finished loading
//We assume that somewhere in the code you have a username and password. I'm not going to go into the details of this, but you could use cookies or javascript variables, depending on your needs.
$(document).ready(function() {
$.get('php/Services.php',
{'action' : 'getListOfFiles', 'user' : username, 'password' : password},
function(data) {
//This should all look familiar if you have used Jquery before.
//This code doesn't have any error checking,
//but you should ALWAYS check the data coming back
//from the server to make sure it is safe and isn't null
for (var k=0; k < data.length; k++) {
//for each file in the list, we will
//create a new image element with
//Jquery and append it to our image-area in our DOM
$('#image-area').append('<img src="php/Services.php?action=getFile&fileName=' + data[k] + '&username=user&password=pass" />');
}
}, 'json') //specify what format we want the data back in
})
That's it. The Jquery code will fetch a list of files, then use that list of files to dynamically generate images on the page. Some notes on the AJAX code: You can use POST to retrieve the file list, but you cannot use POST to retrieve the actual files. The is because of the way in which the underlying XMLHttpRequest handles binary data. Just trust me on this one.
This tutorial leaves a lot for you to flesh out, but it should be a good starting point for the concepts.