PHP
downloads | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | conferences | my php.net

search for in the

クッキー(Cookies)> <機能
Last updated: Fri, 18 Jul 2008

view this page in

PHP による HTTP 認証

PHP による HTTP 認証のフックは、 Apache モジュールとして実行した時のみ 有効で、CGI 版では利用できません。Apache モジュール上の PHP スクリプトに おいては、header() 関数を使用して "Authentication Required" メッセージをクライアントブラウザに 送ることが可能です。 これにより、クライアントブラウザではユーザー名とパスワードの入力要求 ウインドウがポップアップ表示されます。一度、ユーザーがユーザー名と パスワードを入力すると、PHP スクリプトを含むその URL は、次回以降、 定義済みの変数 PHP_AUTH_USER と、 PHP_AUTH_PW と、 PHP_AUTH_TYPE にそれぞれユーザー名、 パスワード、認証型が代入された状態で呼ばれます。 定義済みの変数は、配列 $_SERVER および $HTTP_SERVER_VARS でアクセス可能です。 "Basic" 認証および "Digest" 認証 (PHP 5.1.0 以降) の両者がサポートされています。詳細は、 header()を参照ください。

注意: PHP バージョンに関する注意 $_SERVERのような スーパーグローバルは、 PHP » 4.1.0 以降で利用可能となりました。

ページ上でクライアント認証を強制するスクリプトの例を以下に示します。

例1 Basic HTTP 認証の例

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    
header("WWW-Authenticate: Basic realm=\"My Realm\"");
    
header("HTTP/1.0 401 Unauthorized");
    echo 
"ユーザーがキャンセルボタンを押した時に送信されるテキスト\n";
    exit;
} else {
    echo 
"<p>こんにちは、{$_SERVER['PHP_AUTH_USER']} さん。</p>";
    echo 
"<p>あなたは、{$_SERVER['PHP_AUTH_PW']} をパスワードとして入力しました。</p>";
}
?>

例2 Digest HTTP 認証の例

この例は、シンプルな Digest HTTP 認証スクリプトをどの様に実装するか を示しています。 その他情報については、» 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(
'ユーザーがキャンセルボタンを押した時に送信されるテキスト');
}


// PHP_AUTH_DIGEST 変数を精査する
if (!($data http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
    !isset(
$users[$data['username']]))
    die(
'誤った証明書です!');


// 有効なレスポンスを生成する
$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(
'誤った証明書です!');

// OK, 有効なユーザー名とパスワードだ
echo 'あなたは次のユーザーとしてログインしています: ' $data['username'];

// http auth ヘッダをパースする関数
function http_digest_parse($txt)
{
    
// データが失われている場合への対応
    
$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$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;
}
?>

注意: 互換性に関する注意 HTTPヘッダ行をコーディングする際には注意を要します。全てのクライアントへの 互換性を最大限に保証するために、キーワード "Basic" には、 大文字の"B"を使用して書くべきです。realm文字列は(一重引用符ではなく) 二重引用符で括る必要があります。また、HTTP/1.0 401 ヘッダ行のコード 401 の前には、 1つだけ空白を置く必要があります。 認証パラメータは、上のダイジェスト認証の例にあるように カンマ区切りで指定しなければなりません。

単に PHP_AUTH_USERおよびPHP_AUTH_PW を出力するのではなく、ユーザー名とパスワードの有効性をチェックしたいと 思うかもしれません。 その場合、クエリーをデータベースに送るか、ある dbm ファイル中の ユーザーを調べるといったことをすることになるでしょう。

バグのある Internet Explorer ブラウザには注意してください。このブラ ウザは、ヘッダの順序に関してとてもうるさいようです。今のところ、 HTTP/1.0 401 ヘッダの前に WWW-Authenticate ヘッダを送るのが効果があるようです。

PHP 4.3.0 以降、誰かが従来の外部機構による認証を行ってきたページの パスワードを暴くようなスクリプトを書くことを防ぐために、 特定のページに関して外部認証が可能でかつ セーフモード が有効の場合、 PHP_AUTH 変数はセットされません。 この場合、外部認証されたユーザーかどうかを確認するために REMOTE_USER 変数、すなわち、 $_SERVER['REMOTE_USER'] を使用することができます。

注意: 設定上の注意 PHP は、外部認証が動作しているかどうかの判定を AuthType ディレクティブの有無で行います。

しかし、上記の機能も、認証を要求されないURLを管理する人が同じサーバー にある認証を要するURLからパスワードを盗むことを防ぐわけではありませ ん。

サーバーからレスポンスコード 401 を受けた際に、Netscape Navigatorおよび Internet Explorer は共にローカルブラウザのウインドウ上の認証キャッシュを 消去します。この機能により、簡単にユーザーを"ログアウト"させ、強制的に ユーザー名とパスワードを再入力させることができます。この機能は、 "タイムアウト" 付きのログインや、"ログアウト" ボタンに適用されています。

例3 新規に名前 / パスワードを入力させる HTTP 認証の例

<?php
function authenticate() {
    
header('WWW-Authenticate: Basic realm="Test Authentication System"');
    
header('HTTP/1.0 401 Unauthorized');
    echo 
"このリソースにアクセスする際には有効なログインIDとパスワードを入力する必要があります。\n";
    exit;
}

if (!isset(
$_SERVER['PHP_AUTH_USER']) ||
    (
$_POST['SeenBefore'] == && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
    
authenticate();
} else {
    echo 
"<p>Welcome: {$_SERVER['PHP_AUTH_USER']}<br>";
    echo 
"Old: {$_REQUEST['OldAuth']}";
    echo 
"<form action='{$_SERVER['PHP_SELF']}' METHOD='POST'>\n";
    echo 
"<input type='hidden' name='SeenBefore' value='1'>\n";
    echo 
"<input type='hidden' name='OldAuth' value='{$_SERVER['PHP_AUTH_USER']}'>\n";
    echo 
"<input type='submit' value='Re Authenticate'>\n";
    echo 
"</form></p>\n";
}
?>

この動作は、HTTP Basic 認証の標準に基づいていません。よって、この機能に 依存しないように注意する必要があります。Lynx によるテストの結果、 Lynx は、認証証明書を 401 サーバー応答によりクリアしないことが明らかに なっています。このため、back を押してから foward を再度押すことにより 証明書の要件が変更されない限りリソースをオープンすることができます。 しかし、ユーザは '_' キーを押すことにより認証情報をクリアすることが可能です。

PHP4.3.3 までは、Microsoft の IIS サーバーと CGI 版の PHP の組み合わせでは、 この機能は、IIS の制約により使用することができなかったことにも 注意してください。PHP 4.3.3 以降においてこの機能を使用するには、 IIS の設定の "ディレクトリセキュリティ" の "編集" ボタンを押して "匿名アクセス" のみをオンにしてください。その他のフィールドは オフのままにしてください。

他の制限としては、IIS モジュール (ISAPI) と PHP 4 を使用している場合に、 PHP_AUTH_* 変数が使用できないことがあります。 しかし、代わりにHTTP_AUTHORIZATION を使うことができます。 例えば、次のようなコードを考慮してください。list($user, $pw) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

注意: IIS に関する注意: IIS上 で HTTP 認証を使用する場合、PHP の cgi.rfc2616_headers ディレクティブは0 (デフォルト値) にセットされて いなければなりません。

注意: セーフモード が有効の場合、 WWW-Authenticateヘッダの realm部にスクリプトの uid が追加されます。



クッキー(Cookies)> <機能
Last updated: Fri, 18 Jul 2008
 
add a note add a note User Contributed Notes
PHP による HTTP 認証
silkensedai at online dot fr
16-Apr-2008 07: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
13-Mar-2008 02: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 06: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 08: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
12-Oct-2007 12: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.
fordiman at gmail dot com
02-Aug-2007 03:07
@Whatabrain:
"[E=REMOTE_USER:%{HTTP:Authorization},L] ... didn't work. I couldn't see the variable."

Check $_SERVER['REMOTE_USER'] and $_SERVER['REDIRECT_REMOTE_USER'].  It'll be there.
gbelyh at gmail dot com
27-Jul-2007 09: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 11: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 11: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
03-Apr-2007 10: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 05: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 07: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
21-Feb-2007 02: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
06-Feb-2007 02: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
29-Dec-2006 10: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 05: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 04:21
There are .htaccess which actually works for us (cPanel + phpsuexec) unless others failed. Perhaps it may help someone.

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

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

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

Enjoy!
SlamJam
24-Oct-2006 09: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
11-Oct-2006 06: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 10: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 08:23
While Digest authentication is still far superior to Basic authentication, there are a number of security issues that one must keep in mind.

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

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

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

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

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

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

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

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

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

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

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

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

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

comeagain.php is as follows:

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

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

With this combination, I get things to work. Just make sure not to have apache run htpasswd authentication at the same time, then things get really weird :-).
henrik at laurells dot net
01-Jun-2006 09: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 06:06
# PHP (CGI mode) HTTP Authorization with ModRewrite:
# most right example with header check for non empty:
 RewriteEngine on
 RewriteCond %{HTTP:Authorization}  !^$
 RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}, \
E=PHP_AUTH_USER:%{HTTP:Authorization},L]
cyberscribe at php dot net
08-May-2006 09:47
To implement the Digest authentication mentioned above in PHP < 5.1, try prepending the following:

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

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

<?php

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

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

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

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

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

<?php
if (!isset ($_SESSION['firstauthenticate'])) {
   
session_start();
}
  function
authenticate() {
   
header('WWW-Authenticate: Basic realm="Sistema autentificacin UnoAutoSur"');
   
header('HTTP/1_0 401 Unauthorized');
//    header("Status: 401 Access Denied");
   
echo "Unauthorized\n";
    exit;
  }
 if (!isset(
$_SERVER['PHP_AUTH_USER']) || strcmp ($_SERVER['PHP_AUTH_USER'],$user)!=0 ||
      !isset (
$_SERVER['PHP_AUTH_PW']) || strcmp($_SERVER['PHP_AUTH_PW'],$pass)!=0 || !isset ($_SESSION['firstauthenticate']) || !$_SESSION['firstauthenticate']) {
    
$_SESSION['firstauthenticate']=true;
  
authenticate();
 } else {
           
//I destroy the session var now
   
session_unset();
           
//Your code below
 
}
?>
notter at thisaddress dot com
12-Jan-2006 05: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=\"/\">