章 34. 用 PHP 进行 HTTP 认证

PHP 的 HTTP 认证机制仅在 PHP 以 Apache 模块方式运行时才有效,因此该功能不适用于 CGI 版本。在 Apache 模块的 PHP 脚本中,可以用 header() 函数来向客户端浏览器发送“Authentication Required”信息,使其弹出一个用户名/密码输入窗口。当用户输入用户名和密码后,包含有 URL 的 PHP 脚本将会加上预定义变量 PHP_AUTH_USERPHP_AUTH_PWAUTH_TYPE 被再次调用,这三个变量分别被设定为用户名,密码和认证类型。预定义变量保存在 $_SERVER 或者 $HTTP_SERVER_VARS 数组中。支持“Basic”和“Digest”(自 PHP 5.1.0 起)认证方法。请参阅 header() 函数以获取更多信息。

PHP 版本问题: Autoglobals 全局变量,包括 $_SERVER等,自 PHP 4.1.0 起有效,$HTTP_SERVER_VARS 从 PHP 3 开始有效。

以下是在页面上强迫客户端认证的脚本范例:

例子 34-1. Basic HTTP 认证范例

<?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>";
  }
?>

例子 34-2. Digest HTTP 认证范例

本例演示怎样实现一个简单的 Digest HTTP 认证脚本。更多信息请参考 RFC 2617

<?php
$realm
= 'Restricted area';

//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');


if (!isset(
$_SERVER['PHP_AUTH_DIGEST'])) {
    
header('HTTP/1.1 401 Unauthorized');
    
header('WWW-Authenticate: Digest realm="'.$realm.
           
'" qop="auth" nonce="'.uniqid().'" opaque="'.md5($realm).'"');

    die(
'Text to send if user hits Cancel button');
}

// analize the PHP_AUTH_DIGEST variable
preg_match('/username="(?P<username>.*)",\s*realm="(?P<realm>.*)",\s*nonce="(?P<nonce>.*)",\s*uri="(?P<uri>.*)",\s*response="(?P<response>.*)",\s*opaque="(?P<opaque>.*)",\s*qop=(?P<qop>.*),\s*nc=(?P<nc>.*),\s*cnonce="(?P<cnonce>.*)"/', $_SERVER['PHP_AUTH_DIGEST'], $digest);

if (!isset(
$users[$digest['username']]))
    die(
'Username not valid!');


// generate the valid response
$A1 = md5($digest['username'] . ':' . $realm . ':' . $users[$digest['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$digest['uri']);
$valid_response = md5($A1.':'.$digest['nonce'].':'.$digest['nc'].':'.$digest['cnonce'].':'.$digest['qop'].':'.$A2);

if (
$digest['response'] != $valid_response)
    die(
'Wrong Credentials!');

// ok, valid username & password
echo 'Your are logged in as: ' . $digest['username'];

?>

兼容性问题: 在编写 HTTP 标头代码时请格外小心。为了对所有的客户端保证兼容性,关键字“Basic”的第一个字母必须大写为“B”,分界字符串必须用双引号(不是单引号)引用;并且在标头行 HTTP/1.0 401 中,在 401 前必须有且仅有一个空格。

在以上例子中,仅仅只打印出了 PHP_AUTH_USERPHP_AUTH_PW 的值,但在实际运用中,可能需要对用户名和密码的合法性进行检查。或许进行数据库的查询,或许从 dbm 文件中检索。

注意有些 Internet Explorer 浏览器本身有问题。它对标头的顺序显得似乎有点吹毛求疵。目前看来在发送 HTTP/1.0 401 之前先发送 WWW-Authenticate 标头似乎可以解决此问题。

自 PHP 4.3.0 起,为了防止有人通过编写脚本来从用传统外部机制认证的页面上获取密码,当外部认证对特定页面有效,并且安全模式被开启时,PHP_AUTH 变量将不会被设置。但无论如何,REMOTE_USER 可以被用来辨认外部认证的用户,因此可以用 $_SERVER['REMOTE_USER'] 变量。

配置说明: PHP 用是否有 AuthType 指令来判断外部认证机制是否有效。

注意,这仍然不能防止有人通过未认证的 URL 来从同一服务器上认证的 URL 上偷取密码。

Netscape Navigator 和 Internet Explorer 浏览器都会在收到 401 的服务端返回信息时清空所有的本地浏览器整个域的 Windows 认证缓存。这能够有效的注销一个用户,并迫使他们重新输入他们的用户名和密码。有些人用这种方法来使登录状态“过期”,或者作为“注销”按钮的响应行为。

例子 34-3. 强迫重新输入用户名和密码的 HTTP 认证的范例

<?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";
  }

该行为对于 HTTP 的 Basic 认证标准来说并不是必须的,因此不能依靠这种方法。对 Lynx 浏览器的测试表明 Lynx 在收到 401 的服务端返回信息时不会清空认证文件,因此只要对认证文件的检查要求没有变化,只要用户点击“后退”按钮,再点击“前进”按钮,其原有资源仍然能够被访问。不过,用户可以通过按“_”键来清空他们的认证信息。

同时请注意,在 PHP 4.3.3 之前,由于微软 IIS 的限制,HTTP 认证无法工作在 IIS 服务器的 CGI 模式下。为了能够使其在 PHP 4.3.3 以上版本能够工作,需要编辑 IIS 的设置“目录安全”。点击“编辑”并且只选择“匿名访问”,其它所有的复选框都应该留空。

另一个限制是在 IIS 的 ISAPI 模式下使用 PHP 4 的时候,无法使用 PHP_AUTH_* 变量,而只能使用 HTTP_AUTHORIZATION。例如,考虑如下代码:list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

IIS 注意事项: 要 HTTP 认证能够在 IIS 下工作,PHP 配置选项 cgi.rfc2616_headers 必须设置成 0(默认值)。

注: 如果安全模式被激活,脚本的 UID 会被加到 WWW-Authenticate 标头的 realm 部分。


add a note add a note User Contributed Notes
admin at isprohosting dot com
01-Nov-2006 10:21
There are .htaccess which actually works for us (cPanel + phpsuexec) unless others failed. Perhaps it may help someone.

# PHP (CGI mode) HTTP Authorization with ModRewrite:
RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]

Then you need small piece of php code to parse this line and then everything will work like with mod_php:

if (isset($_SERVER['HTTP_AUTHORIZATION']))
{
$ha = base64_decode( substr($_SERVER['HTTP_AUTHORIZATION'],6) );
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', $ha);
unset $ha;
}

Enjoy!
SlamJam
24-Oct-2006 02:28
(Admins, please remove my last note, I missed changing a German variablename into an English expression).

I used Louis example (03-Jun-2006) and it works well for me (thanks).

However, I added some lines, to make sure, the user does only get the Authentification-Window a few times:

<?php
$realm
= mt_rand( 1, 1000000000)."@YourCompany";
$_SESSION['realm'] = $realm;

// In the beginning, when the realm ist defined:
$_SESSION['CountTrials'] = 1;
?>

And then when it comes to check the authentification (ZEND-Tutorial):

<?php

// Not more than 3 Trials
if (!$auth) {
  
$_SESSION['CountTrials']++;
   if (
$_SESSION['CountTrials'] == 4) { 
      
session_destroy() ;
      
header('Location: noentry.php');
       exit ; 
   } else {
      
header("WWW-Authenticate: Basic realm=".$_SESSION['realm']);
      
header("HTTP/1.0 401 Unauthorized");
       echo
'Authorization Required.';
       exit;
   }
} else {
         echo
'<P>You are authorized!</P>';
}
?>

noentry.php is slightely different from comeagain.php.
roychri at php dot net
11-Oct-2006 11:12
For PHP with CGI, make sure you put the rewrite rule above any other rewrite rule you might have.

In my case, I put this at the top of the .htaccess (below RewriteEngine On):
RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]

My symptom was that the REMOTE_USER (or REDIRECT_REMOTE_USER in my case) was not being set at all.
The cause: I had some other RewriteRule that was kickin in and was set as LAST rule.
I hope this helps.
blah at blah dot com
28-Jul-2006 03:46
Getting PHP Authentication to work with CGI-bin.

You must have mod_rewrite installed for this to work. In the directory (of the file) you want to protect, for the .htaccess file:

# PHP (CGI mode) HTTP Authorization with ModRewrite:
# most right example with header check for non empty:
RewriteEngine on
RewriteCond %{HTTP:Authorization}  !^$
RewriteRule ^test.php$ test.php?login=%{HTTP:Authorization}

Change the Rewrite rule to whatever you want it to be. For simplicity, this example only applies to one file, test.php and only if the HTTP Authorization needs to take place.

In the php file:
<?
if (isset($_GET['login'])) {
  
$d = base64_decode( substr($_GET['login'],6) );
   list(
$name, $password) = explode(':', $d);
   echo
'Name:' . $name . "<br>\n";
   echo
'Password:' . $password . "<br>\n";
} else {
  
header('WWW-Authenticate: Basic realm="My Realm"');
  
header('HTTP/1.0 401 Unauthorized');
   echo
'You are not authorized. Bad user, bad!';
   exit;
}
?>

You need to get rid of the first 6 characters for some reason, then decode the Auth data from its base64 format. Then it's a simple matter of extracting the data. You can even pass the data to the $_SERVER variables $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW']. These are the variables that get the login data if you have PHP running as an Apache module. This is useful for mods or plugins.
web at kwi dot dk
13-Jul-2006 01:23
While Digest authentication is still far superior to Basic authentication, there are a number of security issues that one must keep in mind.

In this respect, the Digest example given above is somewhat flawed, because the nonce never times out or otherwise become invalid. It thus becomes a password-equivalent (although to that specific URL only) and can be used by an eavesdropper to fetch the page at any time in the future, thus allowing the attacker to always access the latest version of the page, or (much worse) repeatedly invoke a CGI script -- for instance, if the user requests the URL "/filemanager?delete=somefile", the attacker can repeat this deletion at any point in the future, possibly after the file has been recreated.

And while it might not be possible to change GET data without reauthentication, cookies and POST data *can* be changed.

To protect against the first problem, the nonce can be made to include a timestamp, and a check added to ensure that nonces older than e.g. 30 minutes result in a new authentication request.

To solve the second problem, a one-time only nonce needs to be generated -- that is, all further requests using a particular nonce must be refused.

One way to do this: When the user requests an action such as "deletefile", store a randomly generated nonce in a session variable, issue a 401 authentication challenge with that nonce, and then check against the stored value when receiving the authentication (and clear the session variable).

This way, although a possible eavesdropper receives the nonce and thus gains the ability to perform the action, he can only perform it once -- and the user was going to perform it anyway. (Only the user or the attacker, but not both, gets to perform the action, so it's safe.)

Of course, at some point, the security can only be improved by switching to HTTPS / SSL / TLS (this is for instance the only way to defend against man-in-the-middle attacks). You decide the level of security.
Louis
04-Jun-2006 06:51
I couldn't get authentication to work properly with any of the examples. Finally, I started from ZEND's tutorial example at:
http://www.zend.com/zend/tut/authentication.php?article=authentication (validate using .htpasswd) and tried to deal with the additional cases. My general conclusion is that changing the realm is the only reliable way to cause the browser to ask again, and I like to thank the person who put that example in the manual, as it got me on the right path. No matter what, the browser refuses to discard the values that it already has in mind otherwise. The problem with changing the realm, of course, is that you don't want to do it within a given session, else it causes a new request for a password. So, here goes, hopefully the spacing isn't too messed up by the cut'n'paste.

I spent the better part of a day getting this to work right. I had a very hard time thinking through what the browser does when it encounters an authentication request: seems to me that it tries to get the password, then reloads the page... so the HTML doesn't get run. At least, this was the case with IE, I haven't tested it with anything else.

<?php
session_start
() ;
if (!isset(
$_SESSION['realm'])) {
      
$_SESSION['realm'] = mt_rand( 1, 1000000000 ).
              
" SECOND level: Enter your !!!COMPANY!!! password.";

      
header( "WWW-Authenticate: Basic realm=".$_SESSION['realm'] );

      
//  Below here runs HTML-wise only if there isn't a $_SESSION,
       // and the browser *can't* set $PHP_AUTH_USER... normally
       // the browser, having gotten the auth info, runs the page
       // again without getting here.
       //  What I'm basically getting to is that the way to get
       // here is to escape past the login screen. I tried
       // putting a session_destroy() here originally, but the
       // problem is that the PHP runs regardless, so the
       // REFRESH seems like the best way to deal with it.
      
echo "<meta http-equiv=\"REFRESH\"
               content=\"0;url=index.php\">"
;
       exit;
       }

if (
$_POST['logout'] == "logout") {
      
session_destroy() ;
      
header('Location: comeagain.php');
       exit ;
       }

// "standard" authentication code here, from the ZEND tutorial above.

comeagain.php is as follows:

<?
session_start();
unset(
$_SESSION['realm']);
session_destroy();
echo
"<html><head><title>Logged Out</title><h1>Logout Page</h1><body>" ;
echo
"You have successfully logged out of TOGEN";
echo
" at ".date("h:m:s")." on ".date("d F Y") ;
echo
"<p><a href=\"index.php\">Login Again</a>" ;
echo
"</body></html>" ;
?>

The idea is to be able to trash the session (and thus reset the realm) without prompting the browser to ask again... because it has been redirected to logout.php.

With this combination, I get things to work. Just make sure not to have apache run htpasswd authentication at the same time, then things get really weird :-).
henrik at laurells dot net
01-Jun-2006 02:36
Above top example for digest mode dosn't work if you have safemode on. You need to add a dash and UID to the compare string to make it work. Something like this;;

$A1 = md5($data['username'].':'.
               $realm.'-'.getmyuid().':'.
               $users[$data['username']]);
kembl at example dot com
23-May-2006 11:06
# PHP (CGI mode) HTTP Authorization with ModRewrite:
# most right example with header check for non empty:
 RewriteEngine on
 RewriteCond %{HTTP:Authorization}  !^$
 RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}, \
E=PHP_AUTH_USER:%{HTTP:Authorization},L]
cyberscribe at php dot net
08-May-2006 02:47
To implement the Digest authentication mentioned above in PHP < 5.1, try prepending the following:

<?php
$headers
= apache_request_headers();
$_SERVER['PHP_AUTH_DIGEST'] = $headers['Authorization'];
?>

or, if you don't like the idea of modifying the global $_SERVER variable directly, just use the first line and then substitute $_SERVER['PHP_AUTH_DIGEST'] in the sample code with $headers['Authorization']. Works great.
ZyX
04-Mar-2006 08:04
Simple PHP Script to login on a Basic Authentication page.

<?php

/* Access Configuration */
define ('x401_host', 'www.example.com');
define ('x401_port', 80);
define ('x401_user', 'your_username');
define ('x401_pass', 'your_password');

/* Function */
function get401Page($file) {
  
$out  = "GET $file HTTP/1.1\r\n";
  
$out .= "Host: ".x401_host."t\r\n";
  
$out .= "Connection: Close\r\n";
  
$out .= "Authorization: Basic ".base64_encode(x401_user.":".x401_pass)."\r\n";
  
$out .= "\r\n";

   if (!
$conex = @fsockopen(x401_host, x401_port, $errno, $errstr, 10))
       return
0;
  
fwrite($conex, $out);
  
$data = '';
   while (!
feof($conex)) {
      
$data .= fgets($conex, 512);
   }
  
fclose($conex);
   return
$data;
}

/* Code */
if ($source = get401Page('/absolute/path/file.php?get=value')) {
  echo
$source;
} else {
  echo
"I can't connect!";
}

?>
djreficul at yahoo dot com
15-Feb-2006 06:14
Well, I think it's easy to make authentification works correctly. I use a session var to force authentication everytime a user visit the logging area.

<?php
if (!isset ($_SESSION['firstauthenticate'])) {
  
session_start();
}
  function
authenticate() {
  
header('WWW-Authenticate: Basic realm="Sistema autentificacin UnoAutoSur"');
  
header('HTTP/1_0 401 Unauthorized');
//    header("Status: 401 Access Denied");
  
echo "Unauthorized\n";
   exit;
  }
 if (!isset(
$_SERVER['PHP_AUTH_USER']) || strcmp ($_SERVER['PHP_AUTH_USER'],$user)!=0 ||
     !isset (
$_SERVER['PHP_AUTH_PW']) || strcmp($_SERVER['PHP_AUTH_PW'],$pass)!=0 || !isset ($_SESSION['firstauthenticate']) || !$_SESSION['firstauthenticate']) {
    
$_SESSION['firstauthenticate']=true;
  
authenticate();
 } else {
          
//I destroy the session var now
  
session_unset();
          
//Your code below
 
}
?>
notter at thisaddress dot com
12-Jan-2006 11:19
A better example of the solution Brian was suggesting [admins: please delete my previous post]

logout.php:

<?php
if (!isset($_GET['quit'])) { ?>
        <h4>To complete your log out, please click "OK" then "Cancel" in
       this <a href="logout.php?quit=y">log in box</a>. Do not fill in a
       password. This should clear your ID and password from the cache of your
       browser.
       <blockquote>Note: Logging in from this particular box is 
       disabled!</blockquote>
       <p>Go <a href="/">back to the site</a>.</h4>
       <?php
} else {
      
header('WWW-Authenticate: Basic realm="This Realm"');
      
header('HTTP/1.0 401 Unauthorized');
      
// if a session was running, clear and destroy it
      
session_start();
      
session_unset();
      
session_destroy();
       echo
"<h3>Logged out!</h3><h4>Go <a href=\"/\">back to the site</a>.</h4>";
}
?>

Note: "This Realm" should be changed to precisely match the name of your realm in your main login.
marco dot moser at oltrefersina dot it
09-Jan-2006 10:29
I suggest to demand user's authentication and management to the web server (by .htaccess, ...):

1. configure a global /logon/ directory with a .htaccess file restricted access

2. use fopen wrapper:

  $hh = @fopen("http://{$_SERVER['PHP_AUTH_USER']}:{$_SERVER['PHP_AUTH_PW']}".
   @{$_SERVER['SERVER_NAME']}/logon/", "r");
  if (!$hh) authenticate(); // usual header WWW-Authenticate ...
  fclose($hh);
sezer yalcin
17-Dec-2005 05:16
none of those 'logout' methods would work well.

Even tricky ones like using cookie to reset cache.

Do not waste your time on this.

Browsers want to keep username and password to help user anyway. Try closing the window, or telling user to restart browser.
oaev at mail dot ru
19-Oct-2005 04:26
Once more time about PHP through CGI.

Sometimes by some reasons (settings) web-server does not allow to set any environment variables through .htaccess file, so method offered by bernard dot paques at bigfoot dot com will not work.

Another way to solve this is to set some GET variable:

file .htaccess (it's just my example, maybe you can find better way):

<IfModule mod_rewrite.c>
   RewriteEngine on
  
   RewriteCond %{QUERY_STRING} ^$
   RewriteRule ([^\s]+).php$ $1.php?BAD_HOSTING=%{HTTP:Authorization}
  
   RewriteCond %{QUERY_STRING} ^(.+)$
   RewriteRule ([^\s]+).php $1.php?%1&BAD_HOSTING=%{HTTP:Authorization}
</IfModule>

a part of php file:

<?php
  
if((empty($_SERVER['PHP_AUTH_USER']) or empty($_SERVER['PHP_AUTH_PW'])) and isset($_REQUEST['BAD_HOSTING']) and preg_match('/Basic\s+(.*)$/i', $_REQUEST['BAD_HOSTING'], $matc))
       list(
$_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode($matc[1]));

?>
somebody
18-Oct-2005 10:20
In the previous example it will not work in IE. In order to have a single script work on both IE and FireFox (and handle the cache problem), you need to use the $_SERVER['HTTP_USER_AGENT'] variable to know which logout version to present to the user.

An full example can be seen in the url (could not post it here due to size restrictions):

http://www.free-php.org/index.php?
cat_select=HTTP&show=HTTP_Authentication

(URL split also due to size restrictions)
niklas[dot]westerberg[at]sizeit[dot]se
07-Oct-2005 06:23
To reset the credentials stored by the browser and effectivly force reauthentication of the user, make a logout link on your page pointing to http://logout:logout@{$_SERVER['SERVER_NAME']}/nice_logout_page.php , presuming that there is no user logout with password logout.

Very simple!

Got it from http://www.webmasterworld.com/forum13/3147.htm
siberion at hotmail dot com
25-Aug-2005 12:11
I came up with another approach to work around the problem of browsers caching WWW authentication credentials and creating logout problems. While most browsers have some kind of way to wipe this information, I prefer having my website to take care of the task instead of relying on the user's sanity.

Even with Lalit's method of creating a random realm name, it was still possible to get back into the protected area using the back button in Firefox, so that didn't work. Here's my solution:

Since browsers attach the credentials to specific URLs, use virtual paths where a component of the path is actually a PHP script, and everything following it is part of the URI, such as:

http://www.example.com/some_dir/login.php/auth/8f631b92/

By choosing a different number for the last component of the URL, browsers can be tricked into thinking that they are dealing with a completely different website, and thus prompting the user for credentials again.

Note that using a random, unrestricted number will still allow the user to hit the back button to get back into the page. You should keep track of this number in a server-side file or database and regenerate it upon each successful login, so that the last number(s) become invalid. Using an invalid number might result in a 403 response or, depending on how you feel that day, a 302 to a nasty website.

Care should be taken when linking from the page generated in this case, since relative links will be relative to the virtual and non-existant directory rather than the true script directory.

Hope this helps somebody.
me at lalit dot org
30-Jun-2005 11:27
A very simple HTTP Authentication script that solves the logout problem. I wasted a lot of time figuring out a way to logout. This one works perfectly fine.

<?php

function auth_user() {
  
$realm = mt_rand( 1, 1000000000 );
  
header('WWW-Authenticate: Basic realm="Realm ID='.$realm.']"');
  
header('HTTP/1.0 401 Unauthorized');
   die(
"Unauthorized access forbidden!");
}

if (!isset(
$_SERVER['PHP_AUTH_USER'])) {
  
auth_user();
} else if (!isset(
$_SERVER['PHP_AUTH_USER'])) {
  
auth_user();
} else if (
$_SERVER['PHP_AUTH_USER'] != $auser || $_SERVER['PHP_AUTH_PW'] != $apass) {
  
auth_user();
} else if (isset(
$_GET['action']) && $_GET['action'] == "logout") {
  
auth_user();
}

// Normal Page Code Here
?>

Hope this helps,
Lalit
snagnever at gmail dot com
19-Jun-2005 06:39
It forces a auth each time the page is accessed:
(maybe can save someone)

<?
header
("Expires: Sat, 01 Jan 2000 00:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: post-check=0, pre-check=0",false);
header("Pragma: no-cache");
session_cache_limiter("public, no-store");
session_start();

function
http_auth()
{
  
$_SESSION['AUTH'] = 1;
  
header('HTTP/1.0 401 Unauthorized');
  
header('WWW-Authenticate: Basic realm="sn4g auth system"');
  
// The actions to be done when the user clicks on 'cancel'
  
exit();
}

if( !isset(
$_SERVER['PHP_AUTH_USER']) or @$_SESSION['AUTH'] != 1 )
{
  
http_auth();
   exit();
}

// Actions do be done when the user has logged

// rest, must clean the session array
$_SESSION = array();
session_destroy();
?>
aplanefan at mail dot com
26-May-2005 09:36
I found a way to log out easily
<?php
ob_start
();
if (!isset(
$_SERVER['PHP_AUTH_USER']) || $_COOKIE['isin'] != "1") {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
setcookie ("isin", "1");
die(
'<a href="orderhan.php">Login</a>');
}
else {
if(
$_SERVER['PHP_AUTH_USER'] == "USER" &&  $_SERVER['PHP_AUTH_PW']== "PASSWORD") {
echo
"you got in";
echo
"<a href='".$_SEVER['PHP_SELF']."?action=logout'>logout</a>";
}
else {
setcookie ("isin", "", time() - 3600);
$url=$_SERVER['PHP_SELF'];
header("location: $url");
}
if(
$_GET['action'] == "logout") {
setcookie ("isin", "", time() - 3600);
$url=$_SERVER['PHP_SELF'];
header("location: $url");
}
}
ob_end_flush();
?>
charly at towebs dot com
30-Apr-2005 01:13
A simpler approach on the post of:
bernard dot paques at bigfoot dot com
24-Sep-2004 01:42

This is another "patch" to the PHP_AUTH_USER and PHP_AUTH_PW server variables problem running PHP as a CGI.

First of all don't forget this fragment of code in your .htaccess (it's the only thing you need to make it work with mod_rewrite):

<IfModule mod_rewrite.c>
   RewriteEngine on
   RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
</IfModule>

Then login.php

<?php
$a
= base64_decode( substr($_SERVER["REMOTE_USER"],6)) ;
if ( (
strlen($a) == 0) || ( strcasecmp($a, ":" )  == 0 ))
{
  
header( 'WWW-Authenticate: Basic realm="Private"' );
  
header( 'HTTP/1.0 401 Unauthorized' );
}
else
{
   list(
$name, $password) = explode(':', $a);
  
$_SERVER['PHP_AUTH_USER'] = $name;
  
$_SERVER['PHP_AUTH_PW']    = $password;

}

echo
'PHP_AUTH_USER =' . $_SERVER['PHP_AUTH_USER'] . '<br>';
echo
'PHP_AUTH_PW =' . $_SERVER['PHP_AUTH_PW'] . '<br>';
echo
'REMOTE_USER =' . $_SERVER['REMOTE_USER'] . '<br>';
?>

First, we decode the base64 encoded string discarding the first 6 characters of "Basic " and then we do a regular validation.
At the end of the script we print the variables to verify it's working. This should be ommited in the production version.

It's a variation of the script by Bernard Paques.
Thanks to him for that snippet.
lexa at toxa dot de
30-Mar-2005 04:17
/**
After many tries, I created a login/logout-mechanism, which works
with Internet Explorer (tested on IE6) and Firefox (tested on V1.0).

I've combined some of the hints given below and used a session as
a second independent memory.

check4login() has to be called on every loading of the page.
 **/

function check4login() {

   $baselink = "http://" . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];

   // start a session and don't let it stop automatically:
   session_set_cookie_params(0);
   session_start();
   setcookie("PHPSESSID", session_id());

   // check if the current loading of the page is the first loading
   // after a logout:
   if ($_SESSION['logout'] != '') {
       unset($_SESSION['logout']);
       //
       // initialize a relogin on Firefox
       // (request login with username "relogin"):
       //
       // CAUTION: After that, relative hyperlinks like
       //  <a href="{$_SERVER['PHP_SELF']}">Link</a>
       // will maybe translated into an absolute hyperlink like
       //  http://relogin:relogin@...
       // which will lead to an error-message in Firefox.
       //
       // So you always have to use absolute hyperlinks like $baselink.
       //
       if (! preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT'])) {
           $link = preg_replace("/^http:\/\/(.*)$/",
                       "http://relogin:relogin@$1", $baselink);
           header("Location: $link");
           exit;
   }    }

   // check if a new realm needs to be generated because
   // it's the first loading of the page (or the first loading
   // after a logout):
   //
   // Remark: The realm is generated with some random signs,
   // because Internet Explorer will forget the username if the
   // realm changes. Unfortunately Firefox doesn't do so.
   if (! isset($_SESSION['realm'])) {
       srand();
       $_SESSION['realm'] = "My Realm      ";
       for ($i = 0; $i < 6; $i++) {
           $_SESSION['realm'] .= substr(".,:;-_'+~=", rand(0, 9), 1);
   }    }

   // check if a user has already logged in before:
   if (isset($_SESSION['user'])) {
       unset($_SESSION['login']);
       return true;
   }

   // check if a user just entered a username and password:
   //
   // is_authorized() has to return 'true' if and only if
   // the username and the passwort given are correct.
   if (isset($_SESSION['login'])) {
       if (is_authorized($_SERVER['PHP_AUTH_USER'],
                   $_SERVER['PHP_AUTH_PW'])) {
           $_SESSION['user'] = $_SERVER['PHP_AUTH_USER'];
           unset($_SESSION['login']);
           return true;
   }    }

   // let the browser ask for a username and a password:
   $_SESSION['login'] = true;
   header("WWW-Authenticate: Basic realm=\"{$_SESSION['realm']}\"");
   header("HTTP/1.0 401 Unauthorized");
   echo "You need to log in before you can access this page.";
   phpinfo();        // - for testing only
   exit;
}

function logout() {

   // to do a logout, all session-variables will be deleted,
   // a variable 'logout' is added:
   $_SESSION = array('logout' => true);
   echo "You were successfully logged out.";
   phpinfo();        // - for testing only
   exit;
}
sl at netcentrex dot net
25-Mar-2005 06:16
This forces an instant re-authentication:

// Force a logout.
function imt_logout()
{
   global $_SESSION;
   global $HTTP_SERVER_VARS;
   global $PHP_SELF;
  
   // We mark the session as requiring a re-auth
   $_SESSION['reauth-in-progress'] = 1;

   // This forces the authentication cache clearing
   header("WWW-Authenticate: Basic realm=\"My Realm\"");
   header("HTTP/1.0 401 Unauthorized");
  
   // In case of the user clicks "cancel" in the dialog box
   print '<a href="http://'.$HTTP_SERVER_VARS['HTTP_HOST'].$PHP_SELF.'">click me</a>';
   exit();
}

// Check login
function imt_login()
{
   global $_SERVER;
   global $_SESSION;
   global $REGISTERED_USERS;

   // the valid_user checks the user/password (very primitive test in this example)
   if (!valid_user($_SERVER['PHP_AUTH_USER'], $REGISTERED_USERS))
   {
       session_destroy();
       header("WWW-Authenticate: Basic realm=\"My Realm\"");
       header("HTTP/1.0 401 Unauthorized");
       exit();
   }

   // OK, the user is authenticated
   $_SESSION['user'] = $_SERVER['PHP_AUTH_USER'];
}

Assuming that your page.php?action=logout forces a reauth on the same page, start your page with:

session_start()
if ($_REQUEST["action"] == "logout")
{
   if (isset($_SESSION['reauth-in-progress']))
   {
       session_destroy();
       header("Location: http://".$HTTP_SERVER_VARS['HTTP_HOST'].$PHP_SELF);
   }
   else
       imt_logout();
}
  
imt_login();
brian at nerdlife dot net
20-Mar-2005 11:30
My solution to the logout conondrum:
<?php
if($_GET[op] == 'logout')
{
 
header('WWW-Authenticate: Basic realm="Click \'Ok\' then \'Cancel\' to Log Out"');
 
header('HTTP/1.0 401 Unauthorized');
  echo
'You have been successfully logged out. Click <a href="index.php">here</a> to log back in.');
  die();
}
if(!isset(
$_SERVER[PHP_AUTH_USER]))
{
 
header('WWW-Authenticate: Basic realm="Site Login"');
 
header('HTTP/1.0 401 Unauthorized');
  echo
'You must enter a valid username and password to access this resource.';
  die();
}
else
{
 
//Validate User
 //If Validated:
 
echo "Welcome. <a href='index.php?op=logout'>Logout?</a>"
}
?>
I assume that if the user is reliable enough to even bother logging out, they are reliable enough to click "ok" then "cancel", thereby logging out and displaying the "logged out" message.
ernstp at winzerware dot de
10-Nov-2004 03:05
Don't like passwords at home or simply don't want access with passwords maybe told form one to another...

You make a configuration file like that:

# Passwort for special IP-Range
IP 192.168.0.
axel:PGWAiIeUxcHOg
sven:ADD1IDbsVHSEo

# Following IP works without password (Keyword 'ALL')
IP 192.168.0.4
ALL

# Passwords for the rest of the world
IP
ernst:INo9dSzfU5sRU
sven:ADD1IDbsVHSEo

<?
$path_log
= "/home/ernst/.htmyway";

// Konfigurationsfile einlesen:
$file = file($path_log);

$login = FALSE;                    // Gets TRUE, when login is valid
$ip = 'world';                    // This is the currend IP while reading the configuration
$access = 'world';                // IP from configuration fitting into $_SERVER['REMOTE_ADDR']

foreach ($file as $zeile)
{
  
$zeile = trim($zeile);

   switch(
true)
   {
       case (
strlen($zeile) == 0 ):                // break for empty lines
          
break;

       case (
substr($zeile, 0, 1) == '#' ):            // break for Komments
          
break;

       case (
substr($zeile, 0, 2) == 'IP' ):        // change IP range
          
$ip = substr($zeile, 3);
           if (
$ip == '')
              
$ip = 'world';

           if (
ereg("^$ip", $_SERVER['REMOTE_ADDR']) )    // fitting IP
          
{
              
$access = $ip;
           }
           break;

       case (
ereg("(.+):(.*)", $zeile, $reg) ):        // scan passwords
          
$logray[$ip][$reg[1]] = $reg[2];
           break;

       case (
$zeile == 'ALL' ):                // if no login is needet
          
$logray[$ip]['all'] = 1;
   }
}

// <for testing>
if ( isset($_GET['logout']) )
{
   unset(
$_SERVER['PHP_AUTH_USER']);
   unset(
$_SERVER['PHP_AUTH_PW']);
}
//  </for testing>

// authorisation test
if ( isset($_SERVER['PHP_AUTH_USER']) AND isset($_SERVER['PHP_AUTH_PW']) )
   if( isset(
$logray[$access][$_SERVER['PHP_AUTH_USER']]) )
       if(
$logray[$access][$_SERVER['PHP_AUTH_USER']] == crypt($_SERVER['PHP_AUTH_PW'], (substr($logray[$access][$_SERVER['PHP_AUTH_USER']], 0, 2))) )
          
$login = TRUE;

if ( isset(
$logray[$access]['all']) )
  
$login = TRUE;

// send login-form if needet
if ( !$login )
{
  
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;
}

// Your running programm...
if ( $login )
{
   echo
"Hello " . $_SERVER['PHP_AUTH_USER'];

?>
<BR>
<form>
<input type='submit' name='logout' value='logout'>
</form>
<?

}
?>
bernard dot paques at bigfoot dot com
25-Sep-2004 12:42
The approach described in the manual works only at servers where PHP is ran as an Apache module. Where PHP is ran as a CGI module, credentials are always empty, whether the user actually fills the authentication box or not.

Apparently the set of Apache variables exposed to PHP is restricted when PHP runs in CGI mode, and this explains why $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] are not set.

The solution is to select one Apache variable actually transmitted to PHP even in CGI mode, and to put in it authentication data submitted by the browser.

Look at the following directive added to the .htaccess file:

<IfModule mod_rewrite.c>
   RewriteEngine on
   RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
</IfModule>

This directive states that, if mod_rewrite is available, credentials gathered from the HTTP header Authorization are put into the $_SERVER['REMOTE_USER'] variable.

To build up on RFC 2617 on HTTP Authentication, if the surfer wishes to send the userid "Aladdin" and password "open sesame", the browser will add the following header attribute to the HTTP request:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Thanks to the rewrite directive put in .htaccess, the triggered PHP script will catch credentials through $_SERVER['REMOTE_USER'] as follows:

$_SERVER['REMOTE_USER'] = Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

From there all you have to do is to decode base64 credentials, and to split the string to retrieve user name and password, as described into the RFC 2617.

Before you use $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'], check if these variables are empty.

If yes, attempt to rebuild them by using $_SERVER['REMOTE_USER'].

The following snippet does exactly that:

<?php
// maybe we have caught authentication data in $_SERVER['REMOTE_USER']
if((!$_SERVER['PHP_AUTH_USER'] || !$_SERVER['PHP_AUTH_USER'])
   &&
preg_match('/Basics+(.*)$/i', $_SERVER['REMOTE_USER'], $matches)) {
   list(
$name, $password) = explode(':', base64_decode($matches[1]));
  
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
  
$_SERVER['PHP_AUTH_PW']    = strip_tags($password);
}
?>

The full script used to build a secured RSS feed is available here:

http://www.yetanothercommunitysystem.com/yacs/scripts/

Of course, this trick works only if the mod_rewrite module is available, and if you have changed the .htaccess file as explained above.
jason
15-Aug-2004 01:12
on the php+mysql auth code by tigran at freenet dot am

There are some security weaknesses.

First
$user
  and
$pass

are both insecure, they could leave this code open to SQL injection, you should always remove invalid characters in both, or at least encode them.

Actually storing passwords as MD5 hashes leaves you less work to secure.

Second security risks
The same mysql user has rights to both update and select, and possibly even insert and on your auth database no less.
Again the SQL inject attack may occur with this., and the end user could then change the users username, password, or anything else in relation to this.

Third items is more of a performance issue,
 
Do you really need to update the database, as updates are slower then selects, and if you do them every time they access the page, you are costing some speed penalty. 

One option, if you want to use sql (I think mysql has it) is memory only databases, and create a table within memory, the stores a unique session identifier for each user, that is logged in, or alternatively if it's a single front end system, you could use db files.
php at cscott dot net
13-Aug-2004 07:18
Note that Microsoft has released a 'security update' which disables the use of username:password@host in http urls.

   http://support.microsoft.com/default.aspx?scid=kb;en-us;834489

The methods described above which rely on this will no longer work in Microsoft browsers, sadly.

You can re-enable this functionality as described at

   http://weblogs.asp.net/cumpsd/archive/2004/02/07/69366.aspx

but your users will probably be unwilling to do this.
chris at schaake dot nu
18-Jun-2004 12:42
A simple script for SSL Client Certificate authentication with a basic authentication fall-back. I use this on my site using LDAP server to check username/passwords and client certificate to user mapping.

<?
// Check if and how we are authenticated
if ($_SERVER['SSL_CLIENT_VERIFY'] != "SUCCESS") { // Not using a client certificate
  
if ((!$_SERVER['PHP_AUTH_USER']) && (!$_SERVER['PHP_AUTH_PW'])) { // Not logged in using basic authentication
      
authenticate(); // Send basic authentication headers
  
}
}

if (
$_SERVER['SSL_CLIENT_S_DN_CN'] != "chris") { // Check CN name of cert

  
if (!(($_SERVER['PHP_AUTH_USER'] == "test") && ($_SERVER['PHP_AUTH_PW'] == "123"))) { // Check username and password
      
authenticate(); // Send basic authentication headers because username and/or password didnot match
  
}
}

phpinfo();

// Call authentication display
function authenticate() {
  
Header("WWW-Authenticate: Basic realm=Website");
      
Header("HTTP/1.0 401 Unauthorized");
      
error401();
       exit;
}
?>

See my website (http://www.schaake.nu/index.php?page=/manuals/sslmanual.xml) for more details on client certificate with Apache and PHP.
mcbethh ( at ) [no spam] op.pl
07-Jun-2004 01:25
There is a nice way to 'fix' missing REMOTE_USER env variable when using PHP as CGI, using mod_rewrite rule like this:

RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
nuno at mail dot ideianet dot pt
14-May-2004 06:33
In Windows 2003 Server/IIS6 with the php4+ cgi I only get HTTP authentication working with:
<?php header("Status: 401 Access Denied"); ?>
with
<?php header('HTTP/1.0 401 Unauthorized'); ?>
doesn't work !
I also need in "Custom Errors" to select the range of "401;1" through "401;5" and click the "Set to Default" button.
Thanks rob at theblip dot com
steuber at aego dot de
07-May-2004 07:15
Quite a good solution for the logout problem:

Just tell browser that it is successfully logged in!
This works as follows:
1. User clicks logout button
2. Script sends 401 Header
3. User does NOT enter a password
4. If no password is entered, script sends 200 Header

So the browser remembers no password as a valid password.

Example:

<?php
if (
       (!isset(
$_SERVER['PHP_AUTH_USER']))
   ||(
           (
$_GET["login"]=="login")
   && !(
             (
$_SERVER['PHP_AUTH_USER']=="validuser")
             && (
$_SERVER['PHP_AUTH_PW']=="validpass")
           )
       )
   ||(
           (
$_GET["logout"]=="logout")
     && !(
$_SERVER['PHP_AUTH_PW']=="")
       )
     ) {
Header("WWW-Authenticate: Basic realm=\"Realm\"");
Header("HTTP/1.0 401 Unauthorized");
echo
"Not logged out...<br>\n";
echo
"<a href=\"index.php?login=login\">Login</a>";
exit;
} else if (
$_SERVER['PHP_AUTH_PW']=="") {
echo
"Logged out...<br>\n";
echo
"<a href=\"index.php?login=login\">Login</a>";
exit;
}
?>
najprogramato at post dot sk
27-Apr-2004 08:08
Don't use apache authentification in plain text. Is more better to use own script to generete new ID which is relevant to password. Apache auth data are sent to every page, so the posible mistake are known.
ian AT iyates DOT co DOR uk
05-Mar-2004 09:32
In my use of HTTP Authentication, I've found that some Apache setups swap around the usual variables.
Here's the fix I made so that you can still use PHP_AUTH_USER and PHP_AUTH_PW. Hope it helps someone

<?php
## Apache server fix ##
if (isset($_SERVER['PHP_AUTH_USER']) && !isset($_ENV['REMOTE_USER']))
  
$_ENV['REMOTE_USER'] = $_SERVER['PHP_AUTH_USER'];
if (isset(
$_SERVER['AUTH_PASSWORD']) && !isset($_ENV['PHP_AUTH_PW']))
  
$_ENV['PHP_AUTH_PW'] = $_SERVER['AUTH_PASSWORD'];
?>
rob at theblip dot com
04-Mar-2004 12:47
Regarding HTTP authentication in IIS with the php cgi 4.3.4, there's one more step. I searched mightily and didn't find this information anywhere else, so here goes. When using HTTP auth with the php CGI, you need to do the following things:

1. In your php.ini file, set "cgi.rfc2616_headers = 0"

2. In Web Site Properties -> File/Directory Security -> Anonymous Access dialog box, check the "Anonymous access" checkbox and uncheck any other checkboxes (i.e. uncheck "Basic authentication," "Integrated Windows authentication," and "Digest" if it's enabled.) Click OK.

3. In "Custom Errors", select the range of "401;1" through "401;5" and click the "Set to Default" button.

It's this last step that is crucial, yet not documented anywhere. If you don't, instead of the headers asking for credentials, IIS will return its own fancy but useless 'you are not authenticated' page. But if you do, then the browser will properly ask for credentials, and supply them in the $_SERVER['PHP_AUTH_*'] elements.
ken_php_net at wolfpackinteractive dot com
26-Dec-2003 05:16
Say you have password and groups files in standard Apache format (htpasswd etc.), but you want to apply authorization based on something other than filename, ie something you can't catch in .htaccess.  You want to emulate the server behavior in PHP -- the equivalent of:

AuthType Basic
AuthName "Members"
AuthUserFile /path/to/.htpasswd
AuthGroupFile /path/to/.groups
require group Members

Here's what I came up with:

<?PHP

$AuthUserFile
= file("/path/to/.htpasswd");
$AuthGroupFile = file("/path/to/.groups");
$group = "Members";
$realm = "Members";

function
authenticate(){
  
header("WWW-Authenticate: Basic realm=\"$realm\"");
  
header('HTTP/1.0 401 Unauthorized');
   echo
"You must enter a valid user name and password to access the requested resource.";
   exit;
}

for(;
1; authenticate()){
   if (!isset(
$HTTP_SERVER_VARS['PHP_AUTH_USER']))
       continue;

  
$user = $HTTP_SERVER_VARS['PHP_AUTH_USER'];
   if(!
preg_grep("/$group: $user$/", $AuthGroupFile))  # (format assumptions)
      
continue;

   if(!(
$authUserLine = array_shift(preg_grep("/$user:.*$/", $AuthUserFile))))
       continue;

  
preg_match("/$user:((..).*)$/", $authUserLine, $matches);
  
$authPW = $matches[1];
  
$salt = $matches[2];
  
$submittedPW = crypt($HTTP_SERVER_VARS['PHP_AUTH_PW'], $salt);
   if(
$submittedPW != $authPW)
       continue;

   break;
}

echo
"You got in!"
?>
Paul
09-Nov-2003 07:53
Here is a extremely easy way to successfully logout.

<?php
if ( $realm == '' )
$realm = mt_rand( 1, 1000000000 );
  
header( 'WWW-Authenticate: Basic realm='.$realm );
?>

To log the user out simply change the value of $realm
bela - iandistudio
21-Oct-2003 01: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 10: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 03: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.
25-Apr-2003 08: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 :

<?php
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)
05-Jun-2002 05: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 11:22
The definitive HTTP authorization code:

<?php
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();
 }
}
?>
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))"

<?php
//////////////////////////////////////////

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
philip at cornado dot com
18-May-2001 06: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 02: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");
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.

<?php
 
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 05: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:

<?php
$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)));