Autenticación HTTP con PHP

Con la función header() se puede enviar un mensaje de "Autenticación requerida" al navegador del cliente para mostrar una ventana emergente donde introducir un usuario y una contraseña. Una vez introducidos estos datos, el URL que contiene el script de PHP será invocado de nuevo con las variables predefinidas PHP_AUTH_USER, PHP_AUTH_PW y AUTH_TYPE establecidas al nombre de usuario, contraseña y tipo de autenticación, respectivamente. Estas variables se encuentran en el array $_SERVER. Se admiten ambos métodos de autenticación, 'Basic' y 'Digest' (desde PHP 5.1.0). Véase la función header() para más información.

Un fragmento de un script de ejemplo que forzaría la autenticación en una página es el siguiente:

Ejemplo #1 Ejemplo de autenticación HTTP 'Basic'

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="Mi dominio"');
header('HTTP/1.0 401 Unauthorized');
echo
'Texto a enviar si el usuario pulsa el botón Cancelar';
exit;
} else {
echo
"<p>Hola {$_SERVER['PHP_AUTH_USER']}.</p>";
echo
"<p>Introdujo {$_SERVER['PHP_AUTH_PW']} como su contraseña.</p>";
}
?>

Ejemplo #2 Ejemplo de autenticación HTTP 'Digest'

Este ejemplo muestra cómo implementar un sencillo script de autenticación HTTP 'Digest'. Para más información lea el » RFC 2617.

<?php
$dominio
= 'Area restringida';

// usuario => contraseña
$usuarios = array('admin' => 'micontraseña', 'invitado' => 'invitado');


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

die(
'Texto a enviar si el usuario pulsa el botón Cancelar');
}


// Analizar la variable PHP_AUTH_DIGEST
if (!($datos = analizar_http_digest($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset(
$usuarios[$datos['username']]))
die(
'Credenciales incorrectas');


// Generar una respuesta válida
$A1 = md5($datos['username'] . ':' . $dominio . ':' . $usuarios[$datos['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$datos['uri']);
$respuesta_válida = md5($A1.':'.$datos['nonce'].':'.$datos['nc'].':'.$datos['cnonce'].':'.$datos['qop'].':'.$A2);

if (
$datos['response'] != $respuesta_válida)
die(
'Credenciales incorrectas');

// Todo bien, usuario y contraseña válidos
echo 'Se ha identificado como: ' . $datos['username'];


// Función para analizar la cabecera de autenticación HTTP
function analizar_http_digest($txt)
{
// Protección contra datos ausentes
$partes_necesarias = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$datos = array();
$claves = implode('|', array_keys($partes_necesarias));

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

foreach (
$coincidencias as $c) {
$datos[$c[1]] = $c[3] ? $c[3] : $c[4];
unset(
$partes_necesarias[$c[1]]);
}

return
$partes_necesarias ? false : $datos;
}
?>

Nota: Sobre la compatibilidad

Hay que tener cuidado al codificar las líneas de cabeceras HTTP. Para garantizar la mayor compatibilidad con todos los clientes, la palabra 'Basic' debe escribirse con 'B' mayúscula, la cadena del dominio debe estar entre comillas dobles (no simples), y debería haber exactamente un espacio precediendo al código 401 de la línea de la cabecera HTTP/1.0 401. Los parámetros de autenticación deben estar separados por comas, como se vió en el ejemplo de 'Digest' anterior.

En lugar de imprimir simplemente PHP_AUTH_USER y PHP_AUTH_PW como se hizo en el ejemplo anterior, podría ser conveniente validar el usuario y la contraseña, tal vez enviando una consulta a una base de datos o buscando el usuario en un fichero dbm.

Cuidado con los navegadores Internet Explorer defectuosos. Parecen ser muy quisquillosos con el orden de las cabeceras. Por ahora, parece ser que el truco está en enviar la cabecera WWW-Authenticate antes que la cabecera HTTP/1.0 401.

Para prevenir que alguien escriba un script que revele la contraseña de una página que se autenticó con un mecanismo externo tradicional, las variables PHP_AUTH no se establecerán si la autenticación externa está habilitada para esa página en particular. Independientemente, se puede emplear REMOTE_USER para identificar al usuario autenticado externamente. De este modo, se podrá usar $_SERVER['REMOTE_USER'].

Nota: Sobre la configuración

PHP utiliza la presencia de una directiva AuthType para determinar si una autenticación externa está en uso.

Observe, sin embargo, que lo anterior no impide que alguien que controle un URL no autenticado pueda robar contraseñas de URL autenticados en el mismo servidor.

Tanto Netscape Navigator como Internet Explorer borran la caché de autenticación de la ventana del navegador local para el dominio al recibr una respuesta 401 del servidor. Esto, en efecto, puede hacer que se cierre la sesión de un usuario, obligándolo a reintroducir su nombre de usuario y contraseña. Algunos utilizan esto para inicios de sesión «expiradas» o proveer un botón de «Cerrar sesión».

Ejemplo #3 Ejemplo de autenticación HTTP forzando un nuevo usuario/contraseña

<?php
function autenticar() {
header('WWW-Authenticate: Basic realm="Sistema de autenticación de prueba"');
header('HTTP/1.0 401 Unauthorized');
echo
"Debe introducir un ID y contraseña de identificación válidos para acceder a este recurso\n";
exit;
}

if (!isset(
$_SERVER['PHP_AUTH_USER']) ||
(
$_POST['VistoAntes'] == 1 && $_POST['UsuarioAntiguo'] == $_SERVER['PHP_AUTH_USER'])) {
autenticar();
} else {
echo
"<p>Bienvenido: " . htmlspecialchars($_SERVER['PHP_AUTH_USER']) . "<br />";
echo
"Antiguo: " . htmlspecialchars($_REQUEST['UsuarioAntiguo']);
echo
"<form action='' method='post'>\n";
echo
"<input type='hidden' name='VistoAntes' value='1' />\n";
echo
"<input type='hidden' name='UsuarioAntiguo' value=\"" . htmlspecialchars($_SERVER['PHP_AUTH_USER']) . "\" />\n";
echo
"<input type='submit' value='Reautenticar' />\n";
echo
"</form></p>\n";
}
?>

El estándar de autenticación HTTP Basic no requiere este funcionamiento, por lo que no se debería depender de ello. Las pruebas con Lynx han mostrado que Lynx no limpia las credenciales de autenticación con una respuesta 401 del servidor, por lo que al presionar «atrás» y luego «adelante» abrirá el recurso nuevamente mientras los requisitos de credenciales no hayan cambiado. Sin embargo, el usuario puede pulsar la tecla '_' para limpiar su información de autenticación.

Para que la Autenticación HTTP funcione en un servidor IIS con la versión CGI de PHP, se debe editar la configuracion de "Seguridad de directorios" de IIS: hacer clic en "Editar" y solo marcar "Acceso anónimo"; todos los demas campos deberían estar sin marcar.

Nota: Sobre IIS:
Para que funcione la Autenticación HTTP con IIS, la directiva de PHP cgi.rfc2616_headers debe establecerse a 0 (el valor predeterminado).

add a note add a note

User Contributed Notes 46 notes

up
49
derkontrollfreak+9hy5l at gmail dot com
9 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
43
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
14
kazakevichilya at gmail dot com
11 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
9
Ome Ko
13 years ago
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
up
6
emmanuel dot keller at net2000 dot ch
21 years ago
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');
?>
up
8
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
7
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
7
php at cscott dot net
19 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
5
jake22 at gmail dot com
8 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
6
admin at isprohosting dot com
17 years ago
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!
up
6
charly at towebs dot com
18 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
9
ceo at l-i-e dot com
13 years ago
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.
up
5
Robb_Bean at gmx dot net
10 years ago
In the german example #2 (digest), the <?php $realm = "Geschützter Bereich"; ?>. As far as I have tested the umlaut ü is problematic, resulting in an password enter infinity loop. In my case it was written in UTF-8. So I suggest using only plain ASCII characters for the realm.
up
5
SlamJam
17 years ago
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.
up
3
najprogramato at post dot sk
19 years ago
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.
up
3
gbelyh at gmail dot com
16 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
3
roychri at php dot net
17 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
5
xsanychx at mail dot ru
11 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
2
Carlos
6 years ago
Simpler WorkAround for missing Authorization header under CGI/FastCGI available in Apache HTTP Server 2.4.13 and later

     CGIPassAuth On

Please don't enable Authorization header with Basic Authentication, is very insecure.
up
2
john_2232 at gmail dot com
8 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
3
Lars Stecken
16 years ago
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
up
3
Louis
17 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
3
Ollie L
13 years ago
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 :)
up
3
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
3
h3ndrik
12 years ago
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);
}
?>
up
3
sergio dot carvalho at gmail dot com
8 years ago
The only effective way I've found to wipe out the PHP_AUTH_DIGEST or PHP_AUTH_USER AND PHP_AUTH_PW credentials is to call the header HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
up
3
djreficul at yahoo dot com
18 years ago
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
}
?>
up
2
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
2
jason
19 years ago
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.
up
2
marco dot moser at oltrefersina dot it
18 years ago
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);
up
2
rob at theblip dot com
20 years ago
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.
up
1
Anonymous
14 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
1
steuber at aego dot de
19 years ago
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;
}
?>
up
1
kembl at example dot com
17 years ago
# 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]
up
1
snagnever at gmail dot com
18 years ago
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();
?>
up
1
Paul
20 years ago
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
up
1
idbobby at rambler dot ru
13 years ago
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;
}

?>
up
-1
spam at angstzustaen dot de
2 years ago
public function loginUser(){
        $username = filter_var($_GET['username'],FILTER_SANITIZE_STRING);
        $pw = filter_var($_GET['pw'],FILTER_SANITIZE_STRING);
        $row = $this->userModel->selectUser($username);

       if (password_verify($pw,$row['pw'])){
           $_SESSION["userId"] = $row['id'];

           $this->isLogin();
       } else{
           new Msg(true,"Benutzername und Passwort stimmen nicht überein!");
       }

    }

A short example for an user Login
up
-1
bitman at bitworks dot de
2 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
0
dan223 at gmail dot com
8 years ago
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://velocitypress.ca/index.php?page=/manuals/) for more details on client certificate with Apache and PHP.
up
0
nuno at mail dot ideianet dot pt
19 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
-1
web at kwi dot dk
17 years ago
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.
up
-1
s dot i dot g at gmx dot com
14 years ago
<?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

?>
up
-1
siberion at hotmail dot com
18 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://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.
up
-5
Whatabrain
17 years ago
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.
up
-2
patrick dot moire at socopa dot fr
3 years ago
Samples Login / Logout script

login.php :

<?php

   
// Initisalition
   
if ($_COOKIE["SESSION"]=='') {
       
setcookie("SESSION", 'AUTH', 0,'/');
       
   
// Identification perdu (time-out ou logoff)
   
} else if ($_SERVER['PHP_AUTH_USER']!='' && $_COOKIE["USER_SESSION"]=='') {
       
$_SERVER['PHP_AUTH_USER'] = '';
    }

   
// Controle identification à la base
   
$ident = executeSQL("SELECT * FROM UTILISATEURS WHERE upper(IDENTIFIANT)=Upper('".$_SERVER['PHP_AUTH_USER']."')");
    if (
$_SERVER['PHP_AUTH_USER']!='' && strtoupper($ident['IDENTIFIANT'])==strtoupper($_SERVER['PHP_AUTH_USER']) && $ident['MOT_DE_PASSE']==$_SERVER['PHP_AUTH_PW']) {
       
$user = $_SERVER['PHP_AUTH_USER'];
       
setcookie("USER_SESSION", $user, time()+300,'/'); // 5 Minutes !
   
    // Mot de passe incorecte : demande identification
   
} else {
       
setcookie("SESSION", '', 1,'/');
       
header('WWW-Authenticate: Basic realm="My Realm"');
       
header('HTTP/1.0 401 Unauthorized');
        die;
    }
   
?>
<html>
    <body >

        Bonjour <?php echo $ident['NOM'].' '.$ident['PRENOM']; ?>
        <br>
        <br>
        <a href="http://logout.php">Deconnexion</a>
    </body>
</html>

logout.php :

<?php
setcookie
("USER_SESSION", '', 1,'/');
header('Location: http://login.php');
?>
To Top