The Server Pages
:: TSP Menu
- Home
- Authors
- Glossary
- RSS Feed

:: Servers
- CPanel
- Hosting
- Linux
- Windows

:: Webmaster
- Databases
- Design
- PHP
- Search Engines

:: Scripts
- Scripts

:: Documentation
- PHP Manual

:: Other
- Errors & Solutions
- TechPunt
- Wojjie

search for in the

Cookies> <Features
[edit] Last updated: Sat, 12 May 2012

view this page in

HTTP authentication with PHP

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. Both "Basic" and "Digest" (since PHP 5.1.0) authentication methods are supported. See the header() function for more information.

Note: PHP Version Note

Superglobals, such as $_SERVER, became available in PHP » 4.1.0.

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

Example #1 Basic 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>";
}
?>

Example #2 Digest HTTP Authentication example

This example shows you how to implement a simple Digest HTTP authentication script. For more information read the » RFC 2617.

<?php
$realm 
'Restricted area';

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


if (empty(
$_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');
}


// analyze the PHP_AUTH_DIGEST variable
if (!($data http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
    !isset(
$users[$data['username']]))
    die(
'Wrong Credentials!');


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

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

// ok, valid username & password
echo 'You are logged in as: ' $data['username'];


// function to parse the http auth header
function http_digest_parse($txt)
{
    
// protect against missing data
    
$needed_parts = array('nonce'=>1'nc'=>1'cnonce'=>1'qop'=>1'username'=>1'uri'=>1'response'=>1);
    
$data = array();
    
$keys implode('|'array_keys($needed_parts));

    
preg_match_all('@(' $keys ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@'$txt$matchesPREG_SET_ORDER);

    foreach (
$matches as $m) {
        
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
        unset(
$needed_parts[$m[1]]);
    }

    return 
$needed_parts false $data;
}
?>

Note: 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. Authentication parameters have to be comma-separated as seen in the digest example above.

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'].

Note: 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 #3 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'] == && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
    
authenticate();
} else {
    echo 
"<p>Welcome: " htmlspecialchars($_SERVER['PHP_AUTH_USER']) . "<br />";
    echo 
"Old: " htmlspecialchars($_REQUEST['OldAuth']);
    echo 
"<form action='' method='post'>\n";
    echo 
"<input type='hidden' name='SeenBefore' value='1' />\n";
    echo 
"<input type='hidden' name='OldAuth' value=\"" htmlspecialchars($_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) and PHP 4, 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)));

Note: 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.



Cookies> <Features
[edit] Last updated: Sat, 12 May 2012
 
add a note add a note User Contributed Notes HTTP authentication with PHP
kazakevichilya at gmail dot com 30-Mar-2012 03:20
In case of CGI/FastCGI you would hot be able to access PHP_AUTH* info because CGI protocol does not declare such variables (that is why their names start from PHP) and server would not pass them to the interpreter. In CGI server should authenticate user itself and pass REMOTE_USER to CGI script after it.

So you need to "fetch" request headers and pass them to your script somehow.

In apache you can do it via environment variables if mod_env is installed.

Following construction in .htaccess copies request header "Authorization" to the env variable PHP_AUTH_DIGEST_RAW

SetEnvIfNoCase ^Authorization$ "(.+)" PHP_AUTH_DIGEST_RAW=$1

You can now access it via $_ENV.

Do not forget to strip auth type ("Digest" in my case) from your env variable because PHP_AUTH_DIGEST does not have it.

If mod_env is not installed you probably have mod_rewrite (everyone has it because of "human readable URLs").

You can fetch header and pass it as GET parameter using rewrite rule:

RewriteRule ^.*$ site.php?PHP_AUTH_DIGEST_RAW=%{HTTP:Authorization} [NC,L]

Here HTTP request header Authorization would be acessible as PHP_AUTH_DIGEST_RAW via $_GET.

---
If you use ZF you probably use Zend_Auth_Adapter_Http to auth user.

It takes Authorization info using "Zend_Controller_Request::getHeader"
This method uses apache_request_header which is likely not to be accessible in old CGI/FastCGI installations or _$_SERVER['HTTP_<HeaderName>] , so you need to put your authentication data, obtained via _GET or ENV to
_$_SERVER['HTTP_AUTHORIZATION'].
It will make ZF work transparently with you solution and I believe any other framework should work also
vog at notjusthosting dot com 23-Feb-2012 05:54
You shouldn't use the "last" ("L") directive in the RewriteRule! This will prevent all further rewrite rules to be skipped whenever a Basic or Digest Auth is given, which is almost certainly not what you want.

So the following lines are sufficient for the .htaccess (or httpd.conf) file:

RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
h3ndrik 24-Oct-2011 12:32
On my configuration with php-cgi - after setting the RewriteRule - the correct variable would be: $_SERVER['REDIRECT_HTTP_AUTHORIZATION']

So the workaround for PhpCGI is:
Set in your .htaccess:

RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization},last]

Php workaround:
<?php
//set http auth headers for apache+php-cgi work around
if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
    list(
$name, $password) = explode(':', base64_decode($matches[1]));
   
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
   
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
}

//set http auth headers for apache+php-cgi work around if variable gets renamed by apache
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['REDIRECT_HTTP_AUTHORIZATION'], $matches)) {
    list(
$name, $password) = explode(':', base64_decode($matches[1]));
   
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
   
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
}
?>
luismontreal at gmail dot com 12-Aug-2011 09:10
Using sessions like this makes the enter auth values at each request

<?php
if($_SESSION['http_logged'] != 1) {
     
$_SERVER['PHP_AUTH_USER'] = '';
     
$_SERVER['PHP_AUTH_PW'] = '';
    }

    if (
$_SERVER['PHP_AUTH_USER'] != $your_username || $_SERVER['PHP_AUTH_PW'] != $your_password ) {
     
$_SESSION['http_logged'] = 1;
     
header('WWW-Authenticate: Basic realm="realm"');
     
header('HTTP/1.0 401 Unauthorized');
      exit;
    } else {
     
$_SESSION['http_logged'] = 0;
    }
?>
Anonymous 06-Mar-2011 12:22
Quickly and without combining, for example, we do a new folder LOGOUT and throw to him .htaccess files with some virtual user:

AuthName "protected area"
Access allow all users Logout

set the error page 401

ErrorDoc 401 "protected area"

and set a link to our new folder <a href="./logout/">logout</ a>
... when I click on enter into a virtual folder with a security dialog to confirm the login without supplying a user name and password and are logged off. Works on FF, IE, Opera ...
ceo at l-i-e dot com 12-Oct-2010 10:51
To force a logout with Basic Auth, you can change the Realm out from under them to a different Realm.

This forces a new set of credentials for a new "Realm" on your server.

You just need to track the Realm name with the user/pass and change it around to something new/random as they log in and out.

I believe that this is the only 100% guaranteed way to get a logout in HTTP Basic Auth, and if it were part of the docs a whole lot of BAD user-contributed comments here could be deleted.
Ollie L 24-Sep-2010 11:16
I tried example 7, and at first I couldn't get it to work. It took me a while to spot that somewhere along the line, probably by the server, a seemingly random number was being added to the realm - so the valid_result variable wasn't calculated using the correct realm.

To get around this, or any similar problems, make the following changes to the example:

Around line 43 (44 if after next step ;) ):
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1, 'realm'=>1);

Before line 24:
$realm = $data['realm'];

These two steps get the real realm used for the authentication request, and substitute it into the "valid_response" query.

Hope this helps :)
idbobby at rambler dot ru 11-Aug-2010 09:31
First of all, sorry for my English.
One more authorization script with logout solution.
In script given by meint_at_meint_dot_net (http://www.php.net/manual/en/features.http-auth.php#93859) rewrite_module is used. If there are any problems with that module and possibilities of administration of the web-server are restricted (including restrictions for use of .htaccess) then you can use this solution.

index.php
---------

<?php

$auth_realm
= 'My realm';

require_once
'auth.php';

echo
"You've logged in as {$_SESSION['username']}<br>";
echo
'<p><a href="?action=logOut">LogOut</a></p>'

?>

auth.php
--------

<?php

$_user_
= 'test';
$_password_ = 'test';

session_start();

$url_action = (empty($_REQUEST['action'])) ? 'logIn' : $_REQUEST['action'];
$auth_realm = (isset($auth_realm)) ? $auth_realm : '';

if (isset(
$url_action)) {
    if (
is_callable($url_action)) {
       
call_user_func($url_action);
    } else {
        echo
'Function does not exist, request terminated';
    };
};

function
logIn() {
    global
$auth_realm;

    if (!isset(
$_SESSION['username'])) {
        if (!isset(
$_SESSION['login'])) {
           
$_SESSION['login'] = TRUE;
           
header('WWW-Authenticate: Basic realm="'.$auth_realm.'"');
           
header('HTTP/1.0 401 Unauthorized');
            echo
'You must enter a valid login and password';
            echo
'<p><a href="?action=logOut">Try again</a></p>';
            exit;
        } else {
           
$user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
           
$password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
           
$result = authenticate($user, $password);
            if (
$result == 0) {
               
$_SESSION['username'] = $user;
            } else {
               
session_unset($_SESSION['login']);
               
errMes($result);
                echo
'<p><a href="">Try again</a></p>';
                exit;
            };
        };
    };
}

function
authenticate($user, $password) {
    global
$_user_;
    global
$_password_;

    if ((
$user == $_user_)&&($password == $_password_)) { return 0; }
    else { return
1; };
}

function
errMes($errno) {
    switch (
$errno) {
        case
0:
            break;
        case
1:
            echo
'The username or password you entered is incorrect';
            break;
        default:
            echo
'Unknown error';
    };
}

function
logOut() {

   
session_destroy();
    if (isset(
$_SESSION['username'])) {
       
session_unset($_SESSION['username']);
        echo
"You've successfully logged out<br>";
        echo
'<p><a href="?action=logIn">LogIn</a></p>';
    } else {
       
header("Location: ?action=logIn", TRUE, 301);
    };
    if (isset(
$_SESSION['login'])) { session_unset($_SESSION['login']); };
    exit;
}

?>
Ome Ko 24-Mar-2010 10:04
a link to http://logout:logout@<?=$_SERVER['HTTP_HOST'];?>/SECRET/ would force a fresh login for the /SECRET directory if no user logout with password logout exists.


[NOTE BY danbrown AT php DOT net: The following note was added by "Anonymous" on 01-APR-2010 (though we presume it's not an April Fool's Day joke).]

this logout method does not work 100% anymore, because of another bulls**t from M$:
http://support.microsoft.com/kb/834489
admin at torrentsbook dot com 29-Oct-2009 05:20
PHP-CGI auth fix:

<?php
if(isset($_SERVER['HTTP_AUTHORIZATION'])) {
 
$auth_params = explode(":" , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
 
$_SERVER['PHP_AUTH_USER'] = $auth_params[0];
  unset(
$auth_params[0]);
 
$_SERVER['PHP_AUTH_PW'] = implode('',$auth_params);
}
?>

Fixes autorization with passwords, containing ":" symbol
ale (at) tana (dot) it 20-Oct-2009 05:52
After we do auth with PHP in Apache, we miss the userid in the access.log file, unless we redefine the LogFormat to be "%{REMOTE_USER}e" rather than "%u", and upon successful authentication we do
<?php
   apache_setenv
('REMOTE_USER', $user);
?>
meint at meint dot net 03-Oct-2009 06:41
The definitive "logout" solution for Basic Authentication

As asserted by many on this page "Basic Authentication" has nog "logout" feature. There is however a standards based method (RFC2617) by which you can make the basic authentication session unique and therefore can emulate a log out function. This function works in all situations and across all browsers which is not the case for most solutions on this page.

The RFC2617 HTTP Authentication standard states that two elements determine the protection space:
- realm, a self chosen, server provided string identifier
- abs_path (i.e. the url)

By making both elements unique and changing them at the log out you will break (and "logout") the basic authentication.

The solution requires the use of mod_rewrite to direct traffic for non-existent resources to the index.php file. Please place a .htaccess file with the following contents in the folder that you wish to protect:

.htaccess:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [NC,L]

Please place an index.php file in the same folder (/auth in this example) with the following content:
<?php

session_start
();

if (empty(
$_SESSION['session_id'])) {
   
session_regenerate_id();
   
$_SESSION['session_id'] = session_id();
   
header("Location: /auth/" . $_SESSION['session_id'] . "/", TRUE, 301);
}

$url_action = (empty($_REQUEST['action'])) ? 'HomePage' : $_REQUEST['action'];
if (isset(
$url_action)) {   
    if (
is_callable($url_action)) {
       
call_user_func($url_action);
    } else {
        print
'Function does not exist, request terminated';
    }
}

function
HomePage() {   
    print
'<h1>Homepage</h1>';
    print
'<p><a href="?action=LogIn">LogIn</a></p>';
    print
'<p><a href="?action=LogOut">LogOut</a></p>';
    print
'<p><a href="?action=SecureContent">Secure Content</a></p>';
}

function
LogIn($url='') {
   
$session_id = $_SESSION['session_id'];
    while (!
IsAuthenticated()) {
       
header('WWW-Authenticate: Basic realm="' . $session_id . '"');
       
header('HTTP/1.1 401 Unauthorized');
        die(
'Authorization Required');
    }
    if (!empty(
$url)) {
        return
TRUE;
    } else {
       
header("Location: /auth/" . $_SESSION['session_id'] . "/", TRUE, 301);   
    }
}   

function
LogOut() {
   
session_destroy();
   
session_unset($_SESSION['session_id']);
   
header("Location: /auth/", TRUE, 301);   
}

function
SecureContent() {
    if (
LogIn("SecureContent")) {
        print
'<h1>Secure Content</h1>';
        print
'<p>This is secure content</p>';
        print
'<p><a href="/auth/' . $_SESSION['session_id'] . '/?action=HomePage">Home Page</a></p>';
    } else {
        print
'<h1>Not Authorized</h1>';
    }
}

function
IsAuthenticated() {
    if (isset(
$_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
       
$httpd_username = filter_var($_SERVER['PHP_AUTH_USER'], FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH|FILTER_FLAG_ENCODE_LOW);
       
$httpd_password = filter_var($_SERVER['PHP_AUTH_PW'], FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH|FILTER_FLAG_ENCODE_LOW);
        if (
$httpd_username == "test" && $httpd_password == "test") {
            return
TRUE;
        } else {
            return
FALSE;
        }
    }
    return
FALSE;
}

?>

Both files need to be put in a folder named /auth for these examples to work. You can change the folder name in the index.php example to suit your own environment.

A the start of the script a unique id is generated through PHP session mechanism and this session id is inserted into the url (hence the requirement for mod_rewrite to catch these urls). A redirect takes place to this new url and the homepage is shown. If you click on login a 401 header is generated using a realm that uses the same session id. If you click logout the current session id is destroyed and you are redirected to the start of the script where a new session id is generated. If you click on a protected item without a login the login function is called.
Anonymous 09-Sep-2009 05:02
The regex in http_digest_parse from Example #2 does not work for me (PHP 5.2.6), because back references are not allowed in a character class.  This worked for me:

<?php

// function to parse the http auth header
function http_digest_parse($txt)
{
  
// protect against missing data
  
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
  
$data = array();

  
preg_match_all('@(\w+)=(?:(?:\'([^\']+)\'|"([^"]+)")|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

   foreach (
$matches as $m) {
      
$data[$m[1]] = $m[2] ? $m[2] : ($m[3] ? $m[3] : $m[4]);
       unset(
$needed_parts[$m[1]]);
   }

   return
$needed_parts ? false : $data;
}

?>
s dot i dot g at gmx dot com 06-Sep-2009 07:34
<?php

// try to mimic cpanel logout style
// only diff is usr & pwd field is cleared when re-login
// tested with ff2 & ie8

session_start();

$username = "test";
$password = "test";

if(isset(
$_GET['logout']))
{
  unset(
$_SESSION["login"]);
  echo
"You have logout ... ";
  echo
"[<a href='" . $_SERVER['PHP_SELF'] . "'>Login</a>]";
  exit;
}

if (!isset(
$_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']) || !isset($_SESSION["login"]))
{
 
header("WWW-Authenticate: Basic realm=\"Test\"");
 
header("HTTP/1.0 401 Unauthorized");
 
$_SESSION["login"] = true;
  echo
"You are unauthorized ... ";
  echo
"[<a href='" . $_SERVER['PHP_SELF'] . "'>Login</a>]";
  exit;
}
else
{
  if(
$_SERVER['PHP_AUTH_USER'] == $username && $_SERVER['PHP_AUTH_PW'] == $password)
  {
    echo
"You have logged in ... ";
    echo
"[<a href='" . $_SERVER['PHP_SELF'] . "?logout'>Logout</a>]";
  }
  else
  {
    unset(
$_SESSION["login"]);
   
header("Location: " . $_SERVER['PHP_SELF']);
  }
}

// content here

?>
norther 24-Jun-2009 07:36
If you are using PHP + IIS, make sure to set HTTP Error 401;5 to Default in IIS directory config. Otherwise it won't prompt for username and password but just show an error message.
jon at toolz4schoolz dot com 16-Apr-2009 01:10
I can't get the second example to work. The regular expression is beyond the scope of this programmer so I rewrote it as seven easy-to-read regular expressions and it works.

Here's the whole function http_digest_parse() that works for me:

<?php

// function to parse the http auth header
function http_digest_parse($txt) {

// protect against missing data
/*
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();

preg_match_all('@(\w+)=(?:([\'"])([^\2]+)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {
    $data[$m[1]] = $m[3] ? $m[3] : $m[4];
    unset($needed_parts[$m[1]]);
}
*/

$res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match);
$data['username'] = $match[1];
$res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match);
$data['nonce'] = $match[1];
$res = preg_match('/nc=([0-9]+)/i', $txt, $match);
$data['nc'] = $match[1];
$res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match);
$data['cnonce'] = $match[1];
$res = preg_match('/qop=([^,]+)/i', $txt, $match);
$data['qop'] = $match[1];
$res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match);
$data['uri'] = $match[1];
$res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match);
$data['response'] = $match[1];

//return $needed_parts ? false : $data;
return $data;

}

?>
Yuriy 15-Feb-2009 12:11
Good day.
Sorry for my english.
This example shows programming "LOGIN", "LOGOUT" and "RE-LOGIN".
This script must use in the protected pages.
For work this script the browser address string must be following:
"http://localhost/admin/?login" - for Login,
"http://localhost/admin/?logout" - for Logout,
"http://localhost/admin/?logout&login" - for Re-Login.
<?php
session_start
();

$authorized = false;

# LOGOUT
if (isset($_GET['logout']) && !isset($_GET["login"]) && isset($_SESSION['auth']))
{
   
$_SESSION = array();
    unset(
$_COOKIE[session_name()]);
   
session_destroy();
    echo
"logging out...";
}

# checkup login and password
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
{
   
$user = 'test';
   
$pass = 'test';
    if ((
$user == $_SERVER['PHP_AUTH_USER']) && ($pass == ($_SERVER['PHP_AUTH_PW'])) && isset($_SESSION['auth']))
    {
   
$authorized = true;
    }
}

# login
if (isset($_GET["login"]) && !$authorized ||
# relogin
   
isset($_GET["login"]) && isset($_GET["logout"]) && !isset($_SESSION['reauth']))
{
   
header('WWW-Authenticate: Basic Realm="Login please"');
   
header('HTTP/1.0 401 Unauthorized');
   
$_SESSION['auth'] = true;
   
$_SESSION['reauth'] = true;
    echo
"Login now or forever hold your clicks...";
    exit;
}
$_SESSION['reauth'] = null;
?>
<h1>you have <? echo ($authorized) ? (isset($_GET["login"]) && isset($_GET["logout"]) ? 're' : '') : 'not '; ?>logged!</h1>
byoung at bigbluehat dot com 16-Dec-2008 06:02
The sample from danja at k0a1a dot net is great for getting started. However, it has some typos. Below is a slightly revised version that should work "out of the box":

<?php
session_start
();

$authorized = false;

if(isset(
$_GET['logout']) && ($_SESSION['auth'])) {
   
$_SESSION['auth'] = null;
   
session_destroy();
    echo
"logging out...";
}

if(isset(
$_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
   
$user = 'test';
   
$pass = 'test';
    if ((
$user == $_SERVER['PHP_AUTH_USER']) && ($pass == ($_SERVER['PHP_AUTH_PW'])) && (!empty($_SESSION['auth']))) {
       
$authorized = true;
    }
}

if (isset(
$_GET["login"]) && (! $authorized)) {
   
header('WWW-Authenticate: Basic Realm="Login please"');
   
header('HTTP/1.0 401 Unauthorized');
   
$_SESSION['auth'] = true;
    print(
'Login now or forever hold your clicks...');
    exit;
}

?>

<h1>you have <? echo ($authorized) ? '' : 'not'; ?> logged!</h1>
danja at k0a1a dot net 17-Sep-2008 04:42
a simplistic login/logout script, mainly for development of user-customizable pages.

<?php
session_start
();

$autorized = false;

if(isset(
$_GET['logout']) && ($_SESSION['auth'])) {
   
$_SESSION['auth'] = null;
   
session_destroy();
    echo
"logging out...";
}

if(isset(
$_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
   
$user = test;
   
$pass = test;
    if ((
$user == $_SERVER['PHP_AUTH_USER']) && ($pass == ($_SERVER['PHP_AUTH_PW'])) && ($_SESSION['auth'])) {
       
$authorized = true;
    }
}

if (isset(
$_GET["login"]) && (! $authorized)) {
   
header('WWW-Authenticate: Basic Realm="Login please"');
   
header('HTTP/1.0 401 Unauthorized');
   
$_SESSION['auth'] = true;
    print(
'Login now or forever hold your clicks...');
    exit;
}

?>

<h1>you have <? echo ($authorized) ? '' : 'not'; ?> logged!</h1>

<?

?>
fakeraol at hotmail dot com 01-Sep-2008 09:29
please remove my other posting, it was just a quickfix.
here is a real solution for both auth-digest-example and the contribution of AlexTM, witch does not work with Internet Explorer:

<?php
function http_digest_parse($digest) {
                    
# edit needed parts, as you  want
   
preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)'.
                   
'=[\'"]?([^\'",]+)@', $digest, $t);
   
$data = array_combine($t[1], $t[2]);
                    
# all parts found?
   
return (count($data)==7) ? $data : false;
}
?>
yaqy at qq dot com 20-Jul-2008 05:38
<?php
/*
* qq: 290359552
* return string : "error" or array("user","pass");
*/
function auth()
{
    if (!isset(
$_SERVER['PHP_AUTH_USER'])) {
       
header('WWW-Authenticate: Basic realm="My Realm"');
       
header('HTTP/1.0 401 Unauthorized');
        return
"error";
    } else {
        return array(
$_SERVER['PHP_AUTH_USER'] , $_SERVER['PHP_AUTH_PW'] );
    }
}
// test:
$au= auth();
print_r( $au );
?>
silkensedai at online dot fr 16-Apr-2008 04:21
Here is my code for basic authentification login/logout.

Include that code before any of your files:
<?php
function redirect_back($http=true, $html=true, $back=NULL){
    if(
is_null($back)){
        if(isset(
$_REQUEST['referer'])){
           
$back = $_REQUEST['referer'];
       
//}elseif(isset($_SERVER['HTTP_REFERER'])){
        //    $back = isset($_SERVER['HTTP_REFERER']);
       
}else{
           
$back = "index.html";
        }
    }
    if(
$http) header("Location: $back");
    if(
$html){
       
$back = htmlspecialchars($back);
        print <<<EOF
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="refresh" content="0; url=
$back">
  </head>
  <body>
    <h1>HTTP/1.0 401 Unauthorized</h1>
    <p><a href="
$back">Go back</a></p>
  </body>
</html>
EOF;
        exit();
    }
}
$userid = 0;
$username = false;
if(isset(
$_SERVER['PHP_AUTH_USER']) and $_SERVER['PHP_AUTH_USER']){
   
$username = $_SERVER['PHP_AUTH_USER'];
   
$userid = authenticate($username, $_SERVER['PHP_AUTH_PW']);
    if(
$userid===false) $username=false; // login failed
}
// If login succeeded (we have a username) or logout succeeded (no username)
if(isset($_GET['login']) && $username || isset($_GET['logout']) && !$username){
   
// Go back
   
redirect_back();
}elseif(isset(
$_GET['login']) || isset($_GET['logout'])){
   
// Ask for password
   
header('WWW-Authenticate: Basic realm=""');
   
header('HTTP/1.0 401 Unauthorized');
   
redirect_back(false);
}
?>

You have to test of $username is not false if you want to be sure the user is authenticated.

Example of use in HTML code:

<?php if($username){ ?>
            <p>You are logged in with username <?php print htmlspecialchars($username); ?>.</p>
            <ul>
                <li><a href="?logout&amp;referer=<?php print htmlspecialchars(urlencode($_SERVER['PHP_SELF'])); ?>">logout</a></li>
            </ul>
<?php }else{ ?>
            <p>You are anonymous.</p>
            <ul>
                <li><a href="?login&amp;referer=<?php print htmlspecialchars(urlencode($_SERVER['PHP_SELF'])); ?>">login</a></li>
            </ul>
<?php } ?>
AlexTM - alextm84 at gmail dot com 12-Mar-2008 11:04
/* Bug fix of my previous note: a dot was missing */

I have written this code to use the Digest
authentication with PHP on both APACHE
and IIS_ISAPI.
This code fixes the differences between
the two modules.

I hope this will help.

AlexTM - Alessandro Cosci

<?php
    session_start
();
   
   
$realm = 'My Realm';
   
$logged = false;
   
//user => password
   
$users = array('user1' => 'psw1', 'user2' => 'psw2'); // ...

   
    // We need to test which server authentication variable to use
    // because the PHP ISAPI module in IIS acts different from CGI
   
if(isset($_SERVER['PHP_AUTH_DIGEST']))
    {
       
$auth_data = $_SERVER['PHP_AUTH_DIGEST'];
       
$isapi = false;
    }
    elseif(isset(
$_SERVER['HTTP_AUTHORIZATION']))
    {
       
$auth_data = $_SERVER['HTTP_AUTHORIZATION'];
       
$isapi = true;
    }
    else
       
$auth_data = "";
    
   
/* The $_SESSION['error_prompted'] variabile is used to ask
       the password again if none given or if the user enters
       a wrong auth. informations. */
   
if (
        (
$auth_data == "") ||
        (isset(
$_SESSION['error_prompted']) && $_SESSION['error_prompted']==true)
       )
    {
       
$uniqid = uniqid(""); // Empty argument for backward compatibility
       
$_SESSION['error_prompted'] = false;
       
header('HTTP/1.1 401 Unauthorized');
       
header('WWW-Authenticate: Digest realm="'.$realm.
              
'" qop="auth" nonce="'.$uniqid.'" opaque="'.md5($realm).'"');
    
        die(
"You're not allowed to access this page.");
    }
    else
    {
       
// We need to retrieve authentication informations from the $auth_data variable
       
if(!$isapi)
        {
           
// CGI doesn't add backslashes to the authentication informations
            // and doesn't prepend the "Digest " string before username.
            // Furthermore it doesn't enclose the "qop" field between double quotes
           
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>.*)"/i', $auth_data, $digest);
        }
        else
        {
           
// ISAP adds backslashes to the authentication informations
            // and prependa the "Digest " string before username.
            // Furthermore it encloses the "qop" field between double quotes
           
preg_match('/digest\susername="(?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>.*)"/i', stripslashes($auth_data), $digest);
           
// Sometimes ISAPI uses qop="auth", and sometimes it uses qop=auth
           
$digest['qop'] = str_replace("\"", "", $digest['qop']);
        }
       
        if (!isset(
$users[$digest['username']]))
        {
           
$_SESSION['error_prompted'] = true;
            die(
'Username not valid!');
        }
        else
        {    
           
// This is the valid response expected
           
$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)
            {
               
$error_message = 'Wrong Credentials!';
               
$_SESSION['error_prompted'] = true;
            }
            else
            {
               
// Ok, valid user/password
               
echo 'You are logged in as: ' . $digest['username'];
               
$logged = true;
            }
        }
    }
      
?>
yuriry at gmail dot com 09-Mar-2008 03:22
This example did not work for me too.  The problem is only the second branch matches.  This is why the trimming was required in the previous post.  In addition, if there are spaces in the realm name, the second branch truncates the name.

I started from a simple regular expression to at least get the right number of matches:

  preg_match_all('@(\w+)=[^,]+,?@',
    $txt, $matches, PREG_SET_ORDER);

The next step was to replace [^,]+ with the right sub-patterns to distinguish between quoted and non-quoted values:

  preg_match_all('@(\w+)=(?:([\'"])([^\'"]+)(?:\2)|(\w+)),?@',
    $txt, $matches, PREG_SET_ORDER);

Branch ([\'"])([^\'"]+)(?:\2) matches quoted values and branch (\w+) matches non-quoted values.

The problem with the first branch is that the middle sub-pattern ([^\'"]+) matches both single and double quotes, and the author definitely intended to use back-references to solve it.  Unfortunately, I could not figure out how to use back-references inside a character class.  From the documentation it does not seem possible and I ended up duplicating ([\'"])([^\'"]+)(?:\2) branch to deal with single and double quotes separately:

  preg_match_all(
    '@(\w+)=(?:([\'])([^\']+)(?:\2)|(["])([^"]+)(?:\4)|(\w+)),?@',
    $txt, $matches, PREG_SET_ORDER);

The assignment to the $data array in the foreach loop needs to be changed to reflect different number of sub-patterns:

  $data[$m[1]] = $m[6] ? $m[6] : ($m[5] ? $m[5] : $m[3]);

Note that no trimming is required and the expression handles spaces in quoted values.  It would also be interesting to know if it is possible to use back-references inside a character class.
Lars Stecken 12-Feb-2008 04:23
To anybody who tried the digest example above and didn't get it to work.

For me the problem seemed to be the deprecated use of '\' (backslash) in the regex instead of the '$' (Dollar) to indicate a backreference. Also the results have to be trimmed off the remaining double and single quotes.

Here's the working example:

// function to parse the http auth header
function http_digest_parse($txt)
{
   
    // protect against missing data
    $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
    $data = array();

    preg_match_all('@(\w+)=(?:([\'"])([^$2]+)$2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
   
    foreach ($matches as $m) {
        $data[$m[1]] = $m[3] ? trim($m[3],"\",'") : trim($m[4],"\",'");
        unset($needed_parts[$m[1]]);
    }
   
    return $needed_parts ? false : $data;
}

Probably there's a more sophisticated way to trim the quotes within the regex, but I couldn't be bothered :-)

Greets, Lars
mt at shrewsbury dot org dot uk 11-Oct-2007 09:28
On my servers here, the standard rewrite spell

RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]

to set $_SERVER[REMOTE_USER] with digest authentication results in the entire digest being bundled into $_SERVER[REMOTE_USER]

I have used this :

RewriteCond %{HTTP:Authorization} username=\"([^\"]+)\"
RewriteRule .* - [E=REMOTE_USER:%1,L]

And it seems to work successfully.
gbelyh at gmail dot com 26-Jul-2007 06:48
Back to the autherisation in CGI mode. this is the full working example:

#  Create the .htaccess file with following contents:
# also you can use the condition (search at this page)
RewriteEngine on
RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]

# In the beginning the script checking the authorization place the code:

$userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;

$userpass = explode(":", $userpass);

if (  count($userpass) == 2  ){
     #this part work not for all.
     #print_r($userpass);die; #<- this can help find out right username and password
     list($name, $password) = explode(':', $userpass);
     $_SERVER['PHP_AUTH_USER'] = $name;
     $_SERVER['PHP_AUTH_PW'] = $password;

}
tonwyatt at yahoo dot com 24-Jul-2007 08:27
Here is my attempt to create a digest authentication class that will log the user in and out without using a cookie,session,db,or file. At the core is this simple code to parse the digest string into variables works for several browsers.
<?php
// explode the digest with multibrowser support by Tony Wyatt 21jun07
public function explodethedigest($instring) {
$quote = '"';
$equal = '=';
$comma = ',';
$space = ' ';
$a = explode( $comma, $instring);
$ax = explode($space, $a[0]);
$b = explode( $equal, $ax[1], 2);
$c = explode( $equal, $a[1], 2);
$d = explode( $equal, $a[2], 2);
$e = explode( $equal, $a[3], 2);
$f = explode( $equal, $a[4], 2);
$g = explode( $equal, $a[5], 2);
$h = explode( $equal, $a[6], 2);
$i = explode( $equal, $a[7], 2);
$j = explode( $equal, $a[8], 2);
$k = explode( $equal, $a[9], 2);
$l = explode( $equal, $a[10], 2);
$parts = array(trim($b[0])=>trim($b[1], '"'), trim($c[0])=>trim($c[1], '"'), trim($d[0])=>trim($d[1], '"'), trim($e[0])=>trim($e[1], '"'), trim($f[0])=>trim($f[1], '"'), trim($g[0])=>trim($g[1], '"'), trim($h[0])=>trim($h[1], '"'), trim($i[0])=>trim($i[1], '"'), trim($j[0])=>trim($j[1], '"'), trim($k[0])=>trim($k[1], '"'), trim($l[0])=>trim($l[1], '"'));

return
$parts;
}
?>
Give it a try at http://tokko.kicks-ass.net/tests/ta1.php Log in with user test password pass or user guest password guest. Go to page two for links to the code. Comments, ideas, suggestions, or critique welcome.
Jack Bates 18-Jul-2007 08:01
In writing the HTTP auth module for the Gallery project, we discovered the following tricks for logging out with HTTP authentication:

Because most web browsers cache HTTP auth credentials, the Gallery logout link didn't work as expected after logging in with HTTP auth. Gallery correctly logged out the active user but the web browser simply logged in again with the next request.

To work around this, the HTTP auth module listens for the Gallery::Logout event and delegates to the httpauth.TryLogout page if necessary: http://gallery.svn.sourceforge.net/viewvc/gallery
/trunk/gallery2/modules/httpauth/TryLogout.inc?view=markup

The TryLogout page tries clearing the browser's authentication cache by as many tricks possible:

    * Ask browser to authenticate with bogus authtype:

GalleryUtilities::setResponseHeader('HTTP/1.0 401 Unauthorized', false);
GalleryUtilities::setResponseHeader('WWW-Authenticate: Bogus', false);

    * Redirect with random username and password. This won't actually clear the browser's authentication cache but will replace it with an invalid username and password. Since Gallery ignores invalid HTTP auth credentials, this effectively logs the user out.

    * Clear Internet Explorer's authentication cache with JavaScript:

 try {ldelim}
   {* http://msdn.microsoft.com/workshop/author
/dhtml/reference/constants/clearauthenticationcache.asp *}
   document.execCommand("ClearAuthenticationCache");
 {rdelim} catch (exception) {ldelim}
 {rdelim}

The TryLogout page redirects to the FinishLogout page for two resons:

   1. To replace the browser's authentication cache with an invalid username and password
   2. To check that the user was indeed logged out. If the user was logged out, the FinishLogout page redirects back to the Gallery application. Otherwise it displays a warning advising the user to manually clear their authentication cache (Clear Private Data in Firefox).

The TryLogout page redirects to the FinishLogout page using JavaScript and falls back on a manual link. It can't use a 302 Found status because the page needs to load for the Internet Explorer JavaScript to execute and because we can't put an invalid username and password in a Location: header.

http://codex.gallery2.org/Gallery2:Modules:httpauth
rovok at web dot de 02-Apr-2007 07:05
People are encouraged NOT to use register_globals, but Example 34.2. of german PHP documentation (http://de.php.net/manual/de/features.http-auth.php) uses register_globals in their example, assumed that the example is the whole script.

There is a <form> which has an <input> with type = "hidden", a name = "SeenBefore" and a value = "1". The Form is submitted by POST, so $SeenBefore should better be accessed by $_POST['SeenBefore'] instead of $SeenBefore.
Dutchdavey 16-Mar-2007 02:28
My sincere thanks to: webmaster at kratia dot com 21-Feb-2007 01:53

The principle is to not allow an invalid PHP_AUTH_USER to exist.

The following easy peasy example using Oracle is based on his simple genius:

///////////////////////////////////////////////////////////////
//
// do_html_header
//
// This function outputs the html header for the page.
//
//////////////////////////////////////////////////////////////////
function initialize_session()
{
   $err=error_reporting(0);
   $connection=oci_connect($_SERVER['PHP_AUTH_USER'],
                          $_SERVER['PHP_AUTH_PW'],$databasename) ;
   error_reporting($err);
   if (!$connection)
   {
      header('WWW-Authenticate: Basic Realm="ZEIP1"');
      header('HTTP/1.0 401 Unauthorized');
      echo "Login Cancelled';
      exit;
   }
   ..
   Normal Code..
   ..
}
Nicolas Merlet - admin(at)merletn.org 05-Mar-2007 03:37
Be careful using http digest authentication (see above, example 34.2) if you have to use the 'setlocale' function *before* validating response with the 'http_digest_parse' function, because there's a conflict with \w in the pattern of 'preg_match_all' function :

In fact, as \w is supposed to be any letter or digit or the underscore character, you must not forgot that this may vary depending on your locale configuration (eg. it accepts accented letters in french)...

Due to this different pattern interpretation by the 'preg_match_all' function, the 'http_digest_parse' function will always return a false result if you have modified your locale (I mean if your locale accepts some extended characters, see http://fr.php.net/manual/en/reference.pcre.pattern.syntax.php for further information).

IMHO, I suggest you not to use setlocale before having your authentication completed...

PS : Here's a non-compatible setlocale declaration...
setlocale ( LC_ALL, 'fr_FR', 'fr', 'FR', 'french', 'fra', 'france', 'French', 'fr_FR.ISO8859-1' ) ;
webmaster at kratia dot com 20-Feb-2007 10:53
This is the simplest form I found to do a Basic authorization with retries.

<?php

$valid_passwords
= array ("mario" => "carbonell");
$valid_users = array_keys($valid_passwords);

$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];

$validated = (in_array($user, $valid_users)) && ($pass == $valid_passwords[$user]);

if (!
$validated) {
 
header('WWW-Authenticate: Basic realm="My Realm"');
 
header('HTTP/1.0 401 Unauthorized');
  die (
"Not authorized");
}

// If arrives here, is a valid user.
echo "<p>Welcome $user.</p>";
echo
"<p>Congratulation, you are into the system.</p>";

?>
mg at evolution515 dot net 05-Feb-2007 10:20
Example for digest doesn't work (at least for me):

use this fix:
--------------
preg_match_all('@(\w+)=(?:(([\'"])(.+?)\3|([A-Za-z0-9/]+)))@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {
    $data[$m[1]] = $m[4] ? $m[4] : $m[5];
    unset($needed_parts[$m[1]]);
}

It's also better to but to put the Auth-Digest-Header in a function and call it on unsuccessful authentification again. Otherwise users only have the chance to submit their username/password just one time.
bleuciell at aol dot com 28-Dec-2006 06:51
For admin , i repair a fault , all is good now
Sorry for my english

It's a piece of code , to give a piece of reflexion about simple auth , we can also cryp login and pass in db , time is here for non-replay , the code isn't finish , but it work , only for reflexion about auth mechanism

<?php
function ky( $txt,$crypt) { $key = md5($crypt); $cpt = 0; $var = "";
for (
$Ctr = 0; $Ctr < strlen($txt); $Ctr++) { if ($cpt == strlen($crypt)) $cpt = 0;
$var.= substr($txt,$Ctr,1) ^ substr($crypt,$cpt,1); $cpt++; } return $var; }

$key = "";$list = 'abcdefghijklmnopqrstuvwxyz0123456789';
for(
$i = 0; $i< 200; $i++) {  $key .= $list{mt_rand() % strlen($list)}; }

function
cryp($txt,$key){ srand((double)microtime()*735412);  $crypt = crypt(rand(0,3895234));$cpt = 0;$var= "";
for (
$Ctr=0; $Ctr < strlen($txt); $Ctr++ ) { if ($cpt == strlen($crypt))$cpt = 0;
$var.= substr($crypt,$cpt,1).( substr($txt,$Ctr,1) ^ substr($crypt,$cpt,1) ); $cpt++; } return base64_encode(ky($var,$key) ); }

function
dcryp($txt,$key){ $txt=ky(base64_decode($txt),$key);$var= "";
for (
$Ctr = 0; $Ctr < strlen($txt); $Ctr++ ) { $md5 = substr($txt,$Ctr,1);$Ctr++; $var.= (substr($txt,$Ctr,1) ^ $md5); }return $var;}

$time= time(); $user = cryp('bubu',$key); $pwd = cryp('bubu-'.$time.'',$key);

function
pwd($j,$key){ $x = dcryp($j,$key); $x = explode('-',$x); return $x[0];}
function
pwd2($j,$key){ $x = dcryp($j,$key); $x = explode('-',$x); return $x[1];}

function
auth(){$realm="Authentification PHPindex";
Header("WWW-Authenticate: Basic realm='".$realm."'");Header("HTTP/1.0  401  Unauthorized");
echo
"Vous ne pouvez accéder à cette page"; }

if( !isset(
$_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['PHP_AUTH_PW']) ) {auth();
} else {
if(
$_SERVER['PHP_AUTH_USER'] == dcryp($user,$key) && $_SERVER['PHP_AUTH_PW'] == pwd($pwd,$key) && $time == pwd2($pwd,$key)) {

          echo
'';

} else{
auth();}}

?>
Whatabrain 10-Nov-2006 01:05
Back to the problem of authenticating in CGI mode... mcbethh suggested using this to set a local variable in php:
RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]

It didn't work. I couldn't see the variable. My solution is pretty round-about, but it works:

RewriteEngine on
RewriteCond %{HTTP:Authorization} !^$
RewriteCond %{REQUEST_METHOD} =GET
RewriteCond %{QUERY_STRING} =""
RewriteRule ^page.php$ page.php?login=%{HTTP:Authorization}$1

This causes the Auth string to be added to the URL if there are no parameters and it's a GET request. This prevents POSTs and parameter lists from being corrupted.

Then, in the PHP script, I store the Auth string as a session cookie.

So the only way to log in to my script is to go to the url with no parameters.
admin at isprohosting dot com 01-Nov-2006 12: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 23-Oct-2006 06:28
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 10-Oct-2006 03: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 27-Jul-2006 07: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 12-Jul-2006 05: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 03-Jun-2006 10: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 31-May-2006 06: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 03: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 07-May-2006 06: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 03-Mar-2006 10: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 14-Feb-2006 08: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 autentificación 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 01: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 12: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 16-Dec-2005 07: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 18-Oct-2005 08: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 02: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 06-Oct-2005 10: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 24-Aug-2005 04: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 03: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 18-Jun-2005 10: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 25-May-2005 01: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 29-Apr-2005 05: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.
sl at netcentrex dot net 24-Mar-2005 08: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 19-Mar-2005 01: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 09-Nov-2004 05: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>
<?

}
?>
jason 14-Aug-2004 05: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 12-Aug-2004 11: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 17-Jun-2004 04: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 06-Jun-2004 05: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 13-May-2004 10: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 06-May-2004 11: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 12: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 04-Mar-2004 11: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 03-Mar-2004 02: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 25-Dec-2003 07: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 08-Nov-2003 09: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 20-Oct-2003 05: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 02: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 16-Jun-2003 07: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.
JKi 13-Feb-2003 09: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 14-Jan-2003 08: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 11-Jan-2003 10: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)
04-Jun-2002 09: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 23-May-2002 03: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 29-Jan-2002 09: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
owld at mail dot ru 30-Aug-2000 10: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 19-May-2000 09: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 09-Feb-2000 05: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)));

 
show source | credits | sitemap | contact | advertising | mirror sites