Recently, during the movement of quite old application to a new server I came across a problem because of the use of HTTP authentication. The problem turned out to be sheet, but at the time of the movement and the first test I had a problem with the diagnosing what is the reasons for this.
Description of the situation
At the beginning, briefly describe the situation, the symptoms appeared. In the following I will show how to deal with the problem.
On the previous server PHP was working himself as a regular Apache module. Since the application was written long time ago, the login system based on HTTP authentication seemed to be sufficient. Moreover, that this was a intranet application made it fine to work. After moving to a new server, it turned out that the authentication window does not stop to pop up even after ceased to supply the correct username and password. The first one went to check the correctness transferred database, user database, the contents of tables. It turned out that everything is ok, so he went to the workshop, a test system log.
Login system based on HTTP authentication
The login system was very easy. Retrieved data from $_SERVER ['PHP_AUTH_USER']
and $_SERVER ['PHP_AUTH_PW']
were compared with those in the database. If no authentication has been successful a message was displayed, and after three seconds the page was refreshed with the re-displaying the authentication dialog.
Shortened index.php file, for example.
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 | <?php if (empty($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="Logowanie"'); header('HTTP/1.0 401 Unauthorized'); sleep(3); exit; }else { DEFINE ('LOGINSALT','TESTOWYSALTLOGOWANIA'); require_once 'class/db/db.class.php'; require_once 'class/login.class.php'; $login_string = md5($_SERVER['PHP_AUTH_USER'].LOGINSALT.md5($_SERVER['PHP_AUTH_PW'])); $logowanie = new login($login_string); $logowanie->login(); if ($logowanie->isLogged()){ // ok } else { include 'notLogged.php'; sleep(3); header('WWW-Authenticate: Basic realm="Logowanie"'); header('HTTP/1.0 401 Unauthorized'); exit; } } ?> |
As you can see it’s very simple, almost trivial solution. With this difference in terms of basic systems based on .htpasswds
that data is stored in the database.
The login class is also very simple. However, in this case, perfectly meets their assumptions. Slimmed version:
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 | <?php /** * Klasa logowania * * @param string $login_string - Ciąg danych logowania md5(login+salt+md5(hasło)) */ class login{ /** * Ciąg logowania * * @var string */ private $_login_string; /** * Stan zalogowania * * @var bool $_logged */ private $_logged = false; /** * Id zalogowanego użytkownika * * @var int */ private $_id; /** * Konstruktor klasy logowania * * @param string $login_string */ public function __construct($login_string){ $this->_login_string = $login_string; } /** * Logowanie użytkownika * * @return bool */ public function login(){ $db = new db_class(DB_HOST,DB_LOGIN,DB_PASS,DB_NAME); $query = "SELECT `ID` FROM `LISTA_USR` WHERE md5(CONCAT(`LOGIN`,'".LOGINSALT."',`PASS`))='".$this->_login_string."' ;"; $db->sqlQuery($query); if ($db->sqlNumRows()>0){ $row = $db->getRow(); $this->_id = $row['ID']; $this->_logged = true; return true; } return false; } /** * Sprawdzenie czy zalogowany * * @return bool $this->_logged */ public function isLogged(){ return $this->_logged; } /** * Pobranie ID zalogowanego użytkownika * * @return int $this->_id; */ public function getUsrId(){ return $this->_id; } } ?> |
Solution very easy. There is nothing more to say about it. Unfortunately, in the CGI mode this solution does not work, because there is no $_SERVER['PHP_AUTH_USER']
and $_SERVER['PHP_AUTH_PW']
in $_SERVER
variable.
Solution
The solution for the lack of required variables is an entry in the .htaccess
, which created a new variable in the $_SERVER
array, which pass login string 1 .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] |
Now, only thing to do is to handle the string on PHP side. Data saved into $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
variable are base64 encoded. In an attempt to get the login and password, you must cut a piece of that variable, decode and break after a colon character.
1 2 3 | <?php list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); ?> |
That would be over. Site is launched, the login and password accepted, the user logged in. But, but … It would be too simple. After clickin on any link (I was using rewriting), it was throwing an 404 error
It turned out that our rewrite rule for HTTP Authentication was too greedy and took all the work on themself, not allowing other rules to work.
Therefore it was necessary to develop a different solution, one that gave friendly urls and $_SERVER['PHP_AUTH_USER']
and $_SERVER['PHP_AUTH_PW']
variables.
Final solution
Like a man sit down and think, it invents. The first idea was good but lacked the basic things, the condition for which it was supposed to be activated. In addition, placing it at the beginning of the .htaccess
file wasn’t a wise thing to do. After moving it at the end of the file, and adding the appropriate condition all started work as expected.
RewriteCond %{HTTP:Authorization} ^(.*) RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1] |
In addition, it was necessary to refactor a script which converts user name and password from the $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
variable, because the previous one was buggy when user password was containing colon sign
1 2 3 4 5 6 7 8 | <?php if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && empty($_SERVER['PHP_AUTH_USER'])) { $auth_params = explode(":" , base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 6))); $_SERVER['PHP_AUTH_USER'] = $auth_params[0]; unset($auth_params[0]); $_SERVER['PHP_AUTH_PW'] = implode('',$auth_params); } ?> |
Summary
As it turned out, cared to without rewriting the login system to the usual form. Besides, we shouldn’t always believe what’s written in the manual:
The HTTP Authentication hooks in PHP are only available when it is running as an Apache module and is hence not available in the CGI version. 2
Probably it’s the time to refactor some of the applications to the “good”, well working in different configurations login system. Systems based on HTTP authentication is fine for embedded computing solutions (routers, managed switches, etc), simple applications should have a usual authentication and authorization systems.
Miło wiedzieć :). Dodam, że takich różnic mod_php/CGI/FastCGI jest więcej i dotyczą one szeregu innych zmiennych, dlatego warto się tym tematem głębiej zainteresować, gdyż szczególnie FastCGI zdobywa ostatnio coraz większą popularność.
Wszystko spoko i tak dalej tylko szkoda, że niedoświadczeni webmasterzy nie wiedzą, że jest to najgorsze rozwiązanie 😉 Dlaczego? Wystarczy sobie poczytać jakie zagrożenia to niesie.