PHP  
downloads | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | my php.net 
search for in the  
<FeaturesCookies>
view the version of this page
Last updated: Thu, 21 Aug 2003

Chapter 16. HTTP authentication with PHP

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. In an Apache module PHP script, it is possible to use the header() function to send an "Authentication Required" message to the client browser causing it to pop up a Username/Password input window. Once the user has filled in a username and a password, the URL containing the PHP script will be called again with the predefined variables PHP_AUTH_USER, PHP_AUTH_PW, and AUTH_TYPE set to the user name, password and authentication type respectively. These predefined variables are found in the $_SERVER and $HTTP_SERVER_VARS arrays. Only "Basic" authentication is supported. See the header() function for more information.

PHP Version Note: Autoglobals, such as $_SERVER, became available in PHP version 4.1.0. $HTTP_SERVER_VARS has been available since PHP 3.

An example script fragment which would force client authentication on a page is as follows:

Example 16-1. HTTP Authentication example

<?php
  if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
  } else {
    echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
    echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
  }
?>

Compatibility Note: Please be careful when coding the HTTP header lines. In order to guarantee maximum compatibility with all clients, the keyword "Basic" should be written with an uppercase "B", the realm string must be enclosed in double (not single) quotes, and exactly one space should precede the 401 code in the HTTP/1.0 401 header line.

Instead of simply printing out PHP_AUTH_USER and PHP_AUTH_PW, as done in the above example, you may want to check the username and password for validity. Perhaps by sending a query to a database, or by looking up the user in a dbm file.

Watch out for buggy Internet Explorer browsers out there. They seem very picky about the order of the headers. Sending the WWW-Authenticate header before the HTTP/1.0 401 header seems to do the trick for now.

As of PHP 4.3.0, in order to prevent someone from writing a script which reveals the password for a page that was authenticated through a traditional external mechanism, the PHP_AUTH variables will not be set if external authentication is enabled for that particular page and safe mode is enabled. Regardless, REMOTE_USER can be used to identify the externally-authenticated user. So, you can use $_SERVER['REMOTE_USER'].

Configuration Note: PHP uses the presence of an AuthType directive to determine whether external authentication is in effect.

Note, however, that the above does not prevent someone who controls a non-authenticated URL from stealing passwords from authenticated URLs on the same server.

Both Netscape Navigator and Internet Explorer will clear the local browser window's authentication cache for the realm upon receiving a server response of 401. This can effectively "log out" a user, forcing them to re-enter their username and password. Some people use this to "time out" logins, or provide a "log-out" button.

Example 16-2. HTTP Authentication example forcing a new name/password

<?php
  function authenticate() {
    header('WWW-Authenticate: Basic realm="Test Authentication System"');
    header('HTTP/1.0 401 Unauthorized');
    echo "You must enter a valid login ID and password to access this resource\n";
    exit;
  }
 
  if (!isset($_SERVER['PHP_AUTH_USER']) ||
      ($_POST['SeenBefore'] == 1 && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
   authenticate();
  } 
  else {
   echo "<p>Welcome: {$_SERVER['PHP_AUTH_USER']}<br>";
   echo "Old: {$_REQUEST['OldAuth']}";
   echo "<form action='{$_SERVER['PHP_SELF']}' METHOD='POST'>\n";
   echo "<input type='hidden' name='SeenBefore' value='1'>\n";
   echo "<input type='hidden' name='OldAuth' value='{$_SERVER['PHP_AUTH_USER']}'>\n";
   echo "<input type='submit' value='Re Authenticate'>\n";
   echo "</form></p>\n";
  }
?>

This behavior is not required by the HTTP Basic authentication standard, so you should never depend on this. Testing with Lynx has shown that Lynx does not clear the authentication credentials with a 401 server response, so pressing back and then forward again will open the resource as long as the credential requirements haven't changed. The user can press the '_' key to clear their authentication information, however.

Also note that until PHP 4.3.3, HTTP Authentication did not work using Microsoft's IIS server with the CGI version of PHP due to a limitation of IIS. In order to get it to work in PHP 4.3.3+, you must edit your IIS configuration "Directory Security". Click on "Edit" and only check "Anonymous Access", all other fields should be left unchecked.

Another limitation is if you're using the IIS module (ISAPI), you may not use the PHP_AUTH_* variables but instead, the variable HTTP_AUTHORIZATION is available. For example, consider the following code: list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

IIS Note:: For HTTP Authentication to work with IIS, the PHP directive cgi.rfc2616_headers must be set to 0 (the default value).

Note: If safe mode is enabled, the uid of the script is added to the realm part of the WWW-Authenticate header.



add a note add a note User Contributed Notes
HTTP authentication with PHP
bela - iandistudio
21-Oct-2003 12:40
Hi there. Thought long and hard to come up with a logout mechanism that erases the variables $PHP_AUTH_USER and $PHP_AUTH_PW. Finally came up with something, it is better than nothing, hope somebody will find it usefull:

logout.php:

<?
header
("Location: http://EnterYourUserName:YourPassword@ServerRoot".$PHP_SELF);
exit;
?>

"EnterYourUserName" and "YourPassword" will be the two variables new value, while "ServerRoot" is your server's domainname (like www.somedomain.com).
You should ensure, that nobody will use "EnterYourUserName" as it's login name.

Happy coding.
ad_ver at inbox dot ru
04-Sep-2003 09:54
Modified script from "jonhaynes at bigfoot dot com" using Oracle logon

<?php 
function authenticate() {
header("WWW-Authenticate: Basic realm=\"My Realm\"");
header("HTTP/1.0 401 Unauthorized");
print(
"You must enter a valid login username and password
to access this resource.\n"
);
exit;
}

if(!isset(
$_SERVER['PHP_AUTH_USER'])){ authenticate(); }
else {
   
$conn = @ OCILogon($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'], "orcl92") ;
    if(!
$conn)
        { 
authenticate(); } 
    else {
OCILogoff($conn);};
}
?>
steve at topozone dot com
17-Jun-2003 02:51
The method described in the text does not appear to work with PHP in cgi mode and Apache-2.x. I seems that Apache has gotten stricter or introduced a bug such that you can initiate the authentication, but Apache seems to try to  authenticate the browser response which always fails because it does not know what to authenticate against and the headers never get passed back to the PHP script.

I didn't try it with PHP as a module.
h1suzuki at hotmail dot com
12-May-2003 02:27
my solution to use SSL for password encryption, because the password is sent to web server as plain text.
insert the following code snipet into the top of secure page.

if (!isset($_SERVER['HTTPS'] || $_SERVER['HTTPS']!="on") {
    header("Location: https://$_SERVER['SERVER_NAME']".
                                    $_SERVER['REQUEST_URI']);
    exit;
}
if (!isset($_SERVER['PHP_AUTH_USER'] || authenticate()) {
    header('WWW-Authenticate: Basic realm="secure"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Authorization Required.';
    exit;
}

first, redirect to the same URI but SSL-enabled page.  then, do authentication.
25-Apr-2003 07:02
@emmanuel dot keller at net2000 dot ch:
The behaviour you report bases on the fact that PHP installed as a CGI program must send CGI status messages instead of HTTP return codes. Therefore, anywhere you normally send "HTTP/1.0 xyz", you have to send "Status: xyz".
JKi
14-Feb-2003 07:38
To clear HTTP authentication cache in Internet Explorer (6 SP1 and later), use "ClearAuthenticationCache" command.

document.execCommand("ClearAuthenticationCache");
emmanuel dot keller at net2000 dot ch
15-Jan-2003 06:14
Some servers won't support the HTTP1.0 specification and will give an error 500 (for instance). This happened with a server where I uploaded an authentication script.

If it happens, you can try the HTTP1.1 header syntax :

header("WWW-Authenticate: Basic realm=\"My Realm\"");
header('status: 401 Unauthorized');
yaroukh at email dot cz
12-Jan-2003 08:42
Here is my solution - works in MSIE and Mozilla.

I use http-authentication only for the first time user accesses his
private page; after valid username and password are provided, he is
recognized using his sessionID and ip ...
the reasons are following:
1) when users changes his password it is not required instantly
   (I find this quite comfortable)
2) auto-login function works fine (unless user click logout)

And here's how i works ...

The "trick" is to pass a temporary username+password to the browser.
(I call it "temporary" because no user account matching these
parameters is neccessary.)

The most essential thing is the following link on user's private page:

===
<?  $url "http://".
       
$username.     // see note 1
       
":".
       
Session_ID().  // see note 2
       
"@localhost/".PROJECT_NAME."/logout.phtml";
?>
<a href="<?=$url?>">logout</a>
===

1) we pass the actual username because MSIE uses this username as
   a "default pre-fill" for the login-window and some hash-string
   would confuse the users.
2) the temporary password is not too important, but there are
   two things we expect from it:
   a) we need to know this string in the logout.phtml script
   b) the string definetely should not match the user's password
      (otherwise user gets logged back instantly); using current
      Session_ID() we are pretty sure this won't happen

This link causes that the temporary login-params are available in
the logout.phtml script.
Using "www-authenticate" header in the logout.phtml script we force
the browser to accept our temporary login-params. (I suppose browser
actually repeats the request and the next time it checks
the login-params sent in the URL; but this is only my guess and
it is not important.)

The logout.phtml code:
===
<?  $query "UPDATE users SET sessionID = NULL ".
       
"WHERE sessionID = '".Session_ID()."'";
     
$mysql->query($query);
     
// because we (me :o) use the sessionID and the ip for
     // the identification we need to clean the sessionID; (I found it
     // a little bit easier to destroy the sessionID in the db than
     // unsetting the cookie and/or destroying+restarting
     // the current session)

     
if($PHP_AUTH_PW != Session_ID()) {
       
// keep asking for the login-params untill PHP_AUTH_PW returned
        // by the browser matches the current Session_ID() (which means
        // that the browser accepted the temporary login-params
        // we sent to it AND FORGOT THE REAL ONES)

       
Header("HTTP/1.0 401 Unauthorized");
       
Header("WWW-Authenticate: Basic realm=\"".PROJECT_NAME."\"");
    }
?>
<html>
    <head>
        <meta http-equiv="author" content="yaroukh at email dot cz">
        <title><?=PROJECT_NAME?></title>
        <link rel="stylesheet" href="style.css" type="text/css">
    </head>
    <body>
         <a href="http://localhost/<?=PROJECT_NAME?>/main.phtml">continue</a>
    </body>
</html>
===

About the "continue" link: the link is not too important, but using it
we can get rid off the temporary login-params which wouldn't look
too aesthetically in the address-bar. :o)
robdemos at cistron dot nl
09-Aug-2002 11:09
Hello all,

I've just found a great article about authentification in php with and without the apache environment, it's as flexible as can be and it works really great and secure.

See for yourself at http://www.devshed.com/Server_Side/PHP/UserAuth/
admin at creationfarm dot com
08-Aug-2002 01:24
I have written a class for HTTP Authentication in PHP. Anyone looking for a shortcut can get it from: http://sourceforge.net/projects/httpauthplus

Feel free to make suggestions and contributions to the class. It needs some improvement.
05-Jun-2002 04:08
A more elegant way to force a new name/password, cf. example 17-2 (if you don't mind passing the old user in the query string):

<?
if (isset($PHP_AUTH_USER))
{
    if (!isset(
$prev_user))
    {
       
header("Location: http://$HTTP_HOST$PHP_SELF?prev_user=$PHP_AUTH_USER");
        exit;
    }
    else
    {
        if (
$PHP_AUTH_USER == $prev_user)
        {
           
header('WWW-Authenticate: Basic realm="Secure"');
           
header('HTTP/1.0 401 Unauthorized');
            exit;
        }
    }
}
else
{
   
header('WWW-Authenticate: Basic realm="Secure"');
   
header('HTTP/1.0 401 Unauthorized');
    exit;
}
?>

The final set of headers is necessary because some browsers seem to unset $PHP_AUTH_USER when the location header is sent.
louis dot carlier at ngroups dot com
24-May-2002 10:22
The definitive HTTP authorization code:

function login_error()
{
 echo "error - login process failed."
}

if (!isset($PHP_AUTH_USER))
{
 header("WWW-Authenticate: Basic realm=\"Mosaic Authorization process\"");
 header("HTTP/1.0 401 Unauthorized");

 //Result if user hits cancel button
 login_error();
}
else
{

 //check the login and password
 if('=>test on login and password<=')
 {
  //User is logged
  ...
  ...
 }
 else
 {
  //This re-asks three times the login and password.
  header( "WWW-Authenticate: Basic realm=\"Test Authentication System\"");
  header("HTTP/1.0 401 Unauthorized");

  //Result if user does not give good login and pass
  login_error();
 }
}
lenny at phpkingdom dot com
22-Feb-2002 04:09
I tried the method posted by
tigran@freenet.am for a logout feature, which seems to be a problem for users of http authentication. Tigran's method is perfect, except that after you log out, you can STILL access the pages by clicking on "cancel" when prompted again by the Java window.
This will trigger the 401 error. But it will also create an entry in the history folder.

You will notice the "forward" button on your browser becomes clickable. You only have to click on the that "forward" button to be able to access the protected pages.

I have found a solution for this problem by using a little Javascript to refresh to another page.

Please go to my website for details:
http://www.phpkingdom.com/source/authentication/auth.phps
sjeffrey at inquesis dot com
30-Jan-2002 07:00
To get it to work with IIS try using this code before setting your "$auth = 0" and the "if (isset($PHP_AUTH_USER) && isset($PHP_AUTH_PW))"

//////////////////////////////////////////

if ($PHP_AUTH_USER == "" && PHP_AUTH_PW == "" && ereg("^Basic ", $HTTP_AUTHORIZATION))
{
  list($PHP_AUTH_USER, $PHP_AUTH_PW) =
    explode(":", base64_decode(substr($HTTP_AUTHORIZATION, 6)));
}

//////////////////////////////////////////

It worked for me on IIS 5 and PHP 4 in ISAPI
jonhaynes at bigfoot dot com
29-Jan-2002 03:25
Restrict access by username, password AND ip address:

<?
function authenticate() {
   
header("WWW-Authenticate: Basic realm=\":-!\"");
   
header("HTTP/1.0 401 Unauthorized");
    print(
"You must enter a valid login username and password
        to access this resource.\n"
);
    exit;
    }
if(!isset(
$PHP_AUTH_USER)){ authenticate(); }
else {
   
$c=mysql_pconnect("server.name","user","password");
   
mysql_select_db("dbname",$c);
   
$q=sprintf("SELECT username,password FROM authenticateTable
        WHERE username='%s' AND password=PASSWORD('%s')
        AND ipaddress='%s'"
,
           
$PHP_AUTH_USER,$PHP_AUTH_PW,$REMOTE_ADDR);
   
$q=mysql_query($q);
    if(
mysql_num_rows($q)==0){ authenticate(); } 
}
?>
eezyeee at yahoo dot com
13-Jun-2001 05:53
This is a good resource for setting up htaccess schemes:

http://www.apacheweek.com/features/userauth

The windows version of apache comes with htpasswd.exe in the apache\bin directory.

The only thing that present problems is you have to change your .htaccess file to point to the created password file (ie C:\directory\passwords.file)....so if you transfer the file back to a *nix server it wont find your file.

One (temporary) workaround is changing your local httpd.conf file to point to a different access file:

AccessFileName htaccess.

You just have to make sure to syncronize your access files.

Im not sure if you can point your htaccess to two password files??
 AuthName "restricted stuff"
 AuthType Basic
 AuthUserFile /usr/local/etc/httpd/users
 AuthUserFile C:\directory\password.file
 require valid-user
philip at cornado dot com
18-May-2001 05:55
You may enjoy this tutorial :
http://www.zend.com/zend/tut/authentication.php
k u d o s at t e l u s p l a n e t dot n e t
05-Apr-2001 01:19
Thanks to yasuo_ohgaki@hotmail.com for the rfc note needed to solve this one. This looks like it flushed out the authentication headers on both Netscape and IE:
Header("WWW-Authenticate: Basic realm=\"Whatever Realm\", stale=FALSE");
marcel at humanique dot com
16-Oct-2000 06:01
The new Mozilla browser doesn't seem to like the switched authentication lines.

This doesn't work (I have build 2000101308):

Header( "WWW-authenticate: Basic realm=\"test\"");
Header( "HTTP/1.0 401 Unauthorized");

The first time you authenticate all seems ok, but the second time it always returns unauthorized.

This works as it should:

Header( "HTTP/1.0 401 Unauthorized");
Header( "WWW-authenticate: Basic realm=\"test\"");
owld at mail dot ru
31-Aug-2000 06:04
Good day.I've solved a problem where IE4 asks for the age one more time after a 401, defeating sending a 401 once to force a user to log on again.

  function  authenticate()  {
    setcookie("noauth","");
    Header( "WWW-authenticate:  Basic realm=\"test\"");
    Header( "HTTP/1.0  401  Unauthorized");
    echo "You must enter user name";
   exit ;
  }
  if  (   !isset($PHP_AUTH_USER) ||  ($logoff==1) && $noauth=="yes"  )   {
    authenticate();
  } 

And logoff link -
 
<a href="samehtml.phtml?logoff=1">Logoff</a></td>

Dmitry Alyekhin
tigran at freenet dot am
20-May-2000 04:31
Here is a code for the public sites enabling both logout bottom and timeout using php+mysql. Working for both browsers.
The part "required" for each user protected page:

<?
function auth () {
       
Header("WWW-Authenticate: Basic realm=\"ArmFN public site\"");
       
Header("HTTP/1.0 401 Unauthorized");
        echo 
"You have to authentificate yourself first \n";
        exit;
}

mysql_connect("localhost","train","") or die("Unable to connect to SQL server"); 
mysql_select_db"train") or die( "Unable to select database"); 

if(!isset(
$PHP_AUTH_USER)) {

$timeout mktime(date(G),date(i)+10,0,date("m"),date("d"),date("Y"));
mysql_query("update users set login='$timeout' where id='$user' and pasw='$pass'") or die("k");

   
auth();

            } else {

   
$pass $PHP_AUTH_PW;
   
$user $PHP_AUTH_USER;

$nowtime mktime(date(G),date(i),0,date("m"),date("d"),date("Y"));
$quer2 mysql_query("select * from users where id='$user' and pasw='$pass' and login > '$nowtime'") or die("kuk2");

    if (
mysql_num_rows($quer2) == "0") {
$timeout mktime(date(G),date(i)+10,0,date("m"),date("d"),date("Y"));
mysql_query("update users set login='$timeout' where id='$user' and pasw='$pass'") or die("k");

auth();
}
        }
?>

You can have a "logout" bottom with hidden $go="logout" form element and then have somewhere this part:

if ($do == "logout") {
mysql_connect("localhost","train","") or die("Unable to connect to SQL server");
mysql_select_db( "train") or die( "Unable to select database");
mysql_query("update users set login=0 where id='$PHP_AUTH_USER' and pasw='$PHP_AUTH_PW'") or die("k");
}
rratboy at pobox dot com
10-Feb-2000 03:59
I had the same problem as above (that is, with apache I can't get the auth info). The solution I found goes like this:

$headers = getallheaders();
$auth=$headers[authorization];
if ($auth=='') { $auth=$headers[Authorization]; }

if($auth=='')
{
    Header("WWW-Authenticate: Basic realm=\"$PROG_NAME\"");
    Header("HTTP/1.0 401 Unauthorized");
}

list($user, $pass) = explode(":", base64_decode(substr($auth, 6)));
fredrick-realm at home dot com
21-Jul-1999 05:02
A few notes on authentication in which it's possible I overlooked some things.  Considering a prior post about using the same salt for all users so you can match passwords; I think it would be better to not do so, as you can figure out the salt from the password and match.  (Example, salt in DES if I'm not mistaken is the first 2 characters)
  Something I've been trying to figure out is secure apache module PHP on a multi-user server. 
  Delima (with postgres)- any user can write a PHP page to read another users databases.  Set your database to connect using username and password, and any user can read your username and password from wherever you place them.  (use PHP function to read it and as it has to be readable by your web process for you to read it, they can) 
  The closest I've come to a solution for this is to run php as a CGI module with suexec or cgiwrap. 
  Hopefuly someone else has a better solution; otherwise, something to think about before you think of your databases as being secure with php interfacing to them.

<FeaturesCookies>
 Last updated: Thu, 21 Aug 2003
show source | credits | sitemap | mirror sites 
Copyright © 2001-2003 The PHP Group
All rights reserved.
This mirror generously provided by: http://php.mirrors.ilisys.com.au/
Last updated: Sat 01 Nov 2003 04:13:36 EST EST