How to restrict user access to content in folders using PHP and Apache .htaccess files

So I was faced with this problem: I needed to restrict access to content(images, files, documents..) in folders, and normally this is done by using a .htacces and .htpasswd file, but I needed more functionallity than this method provided. This extra functionality could be that you simply have a list of users in a database and want to authenticate and authorize users against this database using PHP. In this entry I explain a method I developed to do exactly this.

The method is quite simple

  • A .htaccess file in the base directory has a list of folders you want to restrict access to
  • Whenever a client tries to access some content in one of these folders Apache redirects(rewrite) to a PHP file
  • This PHP file authenticates the user and checks if the user should be allowed access to this folder
  • If successful the PHP file will try to open the file and outputs its contents to the user

The .htaccess file

1
2
3
4
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} ^\/(path\/to\/some\/folder|dummy)\/.*$
RewriteRule !^((.*.php)|(.*\/))$ authorize.php

What this .htaccess file does is to redirect(or rewrite) to the PHP file authorize.php if the request URI(%{REQUEST_URI}) that is, everything after the hostname of the URL address. So if we are at http://www.example.com/somefolder/ex.jpg the %{REQUEST_URI} is /somefolder/ex.jpg. But if the address either ends with .php or a /, indicating that its a folder, it will not redirect to authorize.php. This is accomplished with the regular expression on line 4. If you want to add more exceptions to what kind of files should be redirected or not you can do that in this regular expression. Note that Apache rewrite module mod_rewrite has to be enabled for this to work.

The PHP file (authorize.php)
What’s important in the PHP file below is the code from line 9 to 17 which opens the originally requested file, sets the proper MIME type to the header and send the content of the file to browser. The authentication and authorization should be performed before this code is run to prevent access to the requested file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
// Perform authentication and authorization here
if($hasAccessToFolder)
{
	// Get the file
	if(file_exists($_SERVER['DOCUMENT_ROOT'].$_SERVER['REQUEST_URI']))
	{
		// Open the file for reading
		$fp = fopen($_SERVER['DOCUMENT_ROOT'].$_SERVER['REQUEST_URI'], 'r');
 
		// Set mime type to header
		header('Content-type: '.mime_content_type($_SERVER['DOCUMENT_ROOT'].$_SERVER['REQUEST_URI']));
 
		// Send the contents of the file the browser
		fpassthru($fp);
		fclose($fp);
	}
	else
	{
		// File not found
		die('File not found');
	}
}
else
{
	die('Access denined');
}

PHP Functions for adding and removing paths in the .htaccess file
Below are a couple of functions you can use to manipulate the .htaccess file i.e. add and delete restricted paths to it. The functions will not remove any previous content that may be found in the .htaccess file. So to use these methods simply create an empty .htaccess file and make sure its readable and writeable by the webserver or if you have one already, just make sure its readable and writeable by the webserver.

Note that making the .htaccess file readable and writeable for the webserver represents a certain risk, so make sure that no users can get access to the file through your other webscripts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<?php
 
function openHtaccessFile()
{
	// Determine path to file
	if(substr($_SERVER['DOCUMENT_ROOT'], -1) == '/')
	{
		$pathToFile = $_SERVER['DOCUMENT_ROOT'].'.htaccess';
	}
	else
	{
		$pathToFile = $_SERVER['DOCUMENT_ROOT'].'/.htaccess';
	}
 
	if(!file_exists($pathToFile) || 
		!is_writeable($pathToFile))
	{
		// Create file if it does not exist
		$fp = @fopen($pathToFile, 'x+');
		if(!$fp)
			trigger_error($pathToFile.' file missing or not writeble, 
				please create an empty htaccess file in the document 
				root folder or/and give it permissions allowing the 
				webserver to write to it.', 
				E_USER_ERROR);
 
		fwrite($fp, '
# Generated
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} ^\/(dummy)\/.*$
RewriteRule !^((.*.php)|(.*\/))$ authorize.php
');
		rewind($fp); // Put file pointer to start of file
	}
	else
	{
		// Open file for reading and writing, and put file pointer at the end
		$fp = fopen($pathToFile, 'r+');
 
		// Is the file missing the rewrite rules?
		if(strpos(file_get_contents($pathToFile), 
			'RewriteRule !^((.*.php)|(.*\/))$ authorize.php') === false) 	
		{
			fseek($fp, 0, SEEK_END); // Move file pointer to the end
			fwrite($fp, '
# Generated
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} ^\/(dummy)\/.*$
RewriteRule !^((.*.php)|(.*\/))$ authorize.php
');
			rewind($fp); // Put file pointer to start of file
		}			
	}		
 
	return $fp;
}
 
function validPath(&$path)
{
	if(is_dir($_SERVER['DOCUMENT_ROOT'].$path) || is_dir($_SERVER['DOCUMENT_ROOT'].'/'.$path))
	{
		// Add first / if its not present
		if(substr($path, 0, 1) != '/')
			$path = '/'.$path;
 
		// Remove last / if it is present
		if(substr($path, 0, -1) == '/')
			$path = substr($path, 0, strlen($path)-1);
 
		return true;
	}
	return false;
}
 
function addPathToHtaccess($path)
{
	if(!validPath($path))
		trigger_error('Invalid path', E_USER_ERROR);
 
	$fp = openHtaccessFile();
 
	/*
	 * Remove start slash from $path and escape all 
	 * slashes so it can be used in the regexp in the htaccess
	*/
	$path = str_replace('/', '\/', substr($path, 1));
	$content = ''; // Holds the new lines which we will write back
	while($line = fgets($fp)) // Read all lines
	{
		if(substr($line, 0, 11) == 'RewriteCond')
		{
			// Insert new path
			$content .= 'RewriteCond %{REQUEST_URI} ^\/(' . $path . '|' . substr($line, 31);
		}
		else
		{
			$content .= $line;
		}
	}
 
	rewind($fp); 		// Put file pointer to start of file
	fwrite($fp, $content); 	// Write new lines to file
	fclose($fp);
}
 
function removePathsFromHtaccess(array $paths)
{
	$fp = openHtaccessFile();
 
	// Remove start slash from $path and escape all 
	// slashes so it can be used in the regexp in the htaccess		
	foreach($paths as &$path)
	{
		if(!validPath($path))
			trigger_error('Invalid path', E_USER_ERROR);
 
		$path = str_replace('/', '\/', substr($path, 1));
	}
	$content = ''; // Holds the new lines which we will write back
	while($line = fgets($fp)) // Read all lines
	{
		if(substr($line, 0, 11) == 'RewriteCond')
		{
			// Parse the line and get an array of the old paths
			$segment = substr($line, 31);
			$oldPaths = explode('|', substr($segment, 0, strpos($segment, ')')));
			foreach($oldPaths as $oldPath)
			{
				if(!in_array($oldPath, $paths))
					$newPaths[] = $oldPath;
			}
 
			// Construct the new line
			$content .= 'RewriteCond %{REQUEST_URI} ^\/(' . 
				implode('|', $newPaths) . 
					')\/.*$'."\n";
		}
		else
		{
			$content .= $line;
		}
	}
	ftruncate($fp, 0);		// Delete contents of the old file
	rewind($fp); 		// Put file pointer to start of file
	fwrite($fp, $content); 	// Write new lines to file
	fclose($fp);		
}

7 Responses

  1. Jose says:

    Hi, I don’t know if you are still around, but I am implementing this, however I have some problem when it comes to mobile devices, it sends me to the login page, I use the correct credentials but still send me back to the login page, which is strange since it doesn’t happen on a laptop or a computer, do you have any idea why could that be?

  2. Victor kumilamba says:

    i want to write a code whereby i want to restrict all URl’s that are within the database which i have created in MySQL from accessing the internet

  3. thermal148 says:

    Thank you in advance towards the aid!

  4. Rob says:

    This script fails if there is any space (%20) in the filename

  5. kinderjit singh says:

    I want to restrict users to use my images folder. Please add the answer on my personal site. you can add your suggestions on http://www.jvdinfoways.com in artical page.

  6. Sunny Goyal says:

    if you tell where to put these files on server then it will be more helpful.
    otherwise method looks great but mine is not working i think due to that i dont know where to put these files on server

    • Erik Smistad says:

      All the files can be put on your website’s base folder.
      The mime_content_type has been marked as deprecated for newer versions of PHP. So if thats the problem you need to replace the mime_content_type function with something like:
      $finfo = finfo_open(FILEINFO_MIME_TYPE);
      $mime = finfo_file($finfo, $_SERVER[‘DOCUMENT_ROOT’].$_SERVER[‘REQUEST_URI’]);
      header(‘Content-type: ‘.$mime);

      See http://www.php.net/manual/en/function.finfo-file.php for more information on this

Leave a Reply

Your email address will not be published.