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$coincidenciasPREG_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 y si el modo seguro está habilitado. 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'] == && $_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).

Nota:

Si el modo seguro está habilitado, el uid del script se añade a la parte del dominio de la cabecera WWW-Authenticate.