PHPのお勉強!

PHP TOP

PHP による HTTP 認証

header() 関数を使うと、 "Authentication Required" メッセージをクライアントブラウザに送ることができます。 これにより、クライアントブラウザではユーザー名とパスワードの入力要求 ウインドウがポップアップ表示されます。一度、ユーザーがユーザー名と パスワードを入力すると、PHP スクリプトを含むその URL は、次回以降、 定義済みの変数 PHP_AUTH_USER と、 PHP_AUTH_PW と、 PHP_AUTH_TYPE にそれぞれユーザー名、 パスワード、認証型が代入された状態で呼ばれます。 定義済みの変数は、配列 $_SERVER でアクセス可能です。 "Basic" 認証 のみ がサポートされています。詳細は、 header()を参照ください。

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

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

注意: 互換性に関する注意

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 は、外部認証が動作しているかどうかの判定を AuthType ディレクティブの有無で行います。

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

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

例2 新規に名前 / パスワードを入力させる 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'] == 1 && $_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";
}
?>

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

IIS サーバーと CGI 版の PHP の組み合わせで HTTP 認証を使うには、 IIS の設定の "ディレクトリセキュリティ" の "編集" ボタンを押して "匿名アクセス" のみをオンにしてください。 その他のフィールドはオフのままにしてください。

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

add a note

User Contributed Notes 46 notes

up
74
derkontrollfreak+9hy5l at gmail dot com
10 years ago
Workaround for missing Authorization header under CGI/FastCGI Apache:

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

Now PHP should automatically declare $_SERVER[PHP_AUTH_*] variables if the client sends the Authorization header.
up
57
webmaster at kratia dot com
17 years ago
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>";

?>
up
25
roychri at php dot net
18 years ago
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.
up
32
kazakevichilya at gmail dot com
12 years ago
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
up
23
gbelyh at gmail dot com
17 years ago
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;

}
up
21
Anonymous
15 years ago
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;
}

?>
up
20
vog at notjusthosting dot com
12 years ago
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}]
up
21
john_2232 at gmail dot com
9 years ago
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://www.creativetheory.ca/ /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.
up
22
jake22 at gmail dot com
9 years ago
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://velocitypress.ca/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.
up
17
bitman at bitworks dot de
3 years ago
the alternative text should contain the complete text af a (small) valid HTML-Ressource. It also can contain link relations to CSS.
up
21
charly at towebs dot com
19 years ago
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.
up
22
Louis
18 years ago
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 :-).
up
22
php at cscott dot net
20 years ago
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.
up
18
sjeffrey at inquesis dot com
22 years ago
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
up
11
nuno at mail dot ideianet dot pt
20 years ago
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
up
22
Nicolas Merlet - admin(at)merletn.org
17 years ago
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' ) ;
up
25
Yuriy
15 years ago
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>
up
23
xsanychx at mail dot ru
12 years ago
New auth:

<?php
$login
= 'test_login';
$pass = 'test_pass';

if((
$_SERVER['PHP_AUTH_PW']!= $pass || $_SERVER['PHP_AUTH_USER'] != $login)|| !$_SERVER['PHP_AUTH_USER'])
{
header('WWW-Authenticate: Basic realm="Test auth"');
header('HTTP/1.0 401 Unauthorized');
echo
'Auth failed';
exit;
}
?>
up