Cierre de sesión de autenticación HTTP a través de PHP

151

¿Cuál es la forma correcta de cerrar sesión en la carpeta protegida de autenticación HTTP?

Hay soluciones que pueden lograr esto, pero son potencialmente peligrosas porque pueden tener errores o no funcionan en ciertas situaciones / navegadores. Es por eso que estoy buscando una solución correcta y limpia.

Josef Sábl
fuente
Especifique el propósito de su cierre de sesión. ¿Debería ser esto un cierre de sesión forzado (desactivación del usuario)? ¿Función de cierre de sesión simple para el usuario? ¿Algo más?
Karsten
66
No entiendo por qué esto es importante, pero son ambos casos: desactivación basada en condiciones internas en la aplicación, así como el botón de cierre de sesión típico. Explique por qué es importante, lo editaré directamente en la pregunta.
Josef Sábl el
2
La "solución correcta y limpia" serían los navegadores que tengan su propio botón de cierre de sesión que, al hacer clic, hará que el navegador deje de enviar los encabezados de autenticación ... Uno puede soñar, ¿verdad?
DanMan
1
Web Developer Toolbar tiene ese "botón".
Josef Sábl
Lo que dijo Josef: barra de herramientas de desarrollador web para Firefox ->Miscellaneous -> Clear Private Data -> HTTP Authentication
Yarin

Respuestas:

103

Mu. No existe una forma correcta , ni siquiera una que sea consistente en todos los navegadores.

Este es un problema que proviene de la especificación HTTP (sección 15.6):

Los clientes y agentes de usuario HTTP existentes generalmente retienen la información de autenticación de forma indefinida. HTTP / 1.1. no proporciona un método para que un servidor dirija a los clientes a descartar estas credenciales almacenadas en caché.

Por otro lado, la sección 10.4.2 dice:

Si la solicitud ya incluía credenciales de autorización, la respuesta 401 indica que se ha rechazado la autorización para esas credenciales. Si la respuesta 401 contiene el mismo desafío que la respuesta anterior, y el agente de usuario ya ha intentado la autenticación al menos una vez, entonces el usuario DEBE presentar la entidad que se le dio en la respuesta, ya que esa entidad podría incluir información de diagnóstico relevante.

En otras palabras, es posible que pueda volver a mostrar el cuadro de inicio de sesión (como dice @Karsten ), pero el navegador no tiene que cumplir con su solicitud , así que no dependa demasiado de esta característica (incorrecta).

Piskvor salió del edificio
fuente
9
Este es un error en el RFC. W3C es demasiado flojo para arreglarlo. Muy triste.
Erik Aronesty
Como @Jonathan Hanson sugirió a continuación , puede usar una cookie de seguimiento junto con la autenticación HTTP. Este es el mejor método para mí.
machineaddict
61

Método que funciona bien en Safari. También funciona en Firefox y Opera, pero con una advertencia.

Location: http://[email protected]/

Esto le dice al navegador que abra la URL con un nuevo nombre de usuario, anulando el anterior.

Kornel
fuente
14
De acuerdo con RFC 3986 (URI: sintaxis genérica) sección 3.2.1. (Información del usuario) el uso de user:password@hostestá en desuso. Usar solo http://[email protected]/no es y debería funcionar en la mayoría de los casos.
aef
1
@andho: sí, es una redirección. Debería usarlo con el estado 302.
Kornel
1
Aparentemente, un simple enlace a [email protected] también funciona (un enlace de "desconexión" a esta URL) en lugar de un redireccionamiento http en PHP ... ¿alguna desventaja?
moala
44
Cuidado: el envío del formulario utilizando la ruta relativa puede fallar cuando se realiza después de un reinicio de sesión (inicie sesión con la solicitud de cierre de sesión), porque la dirección aún sería [email protected]/path y no yourserver.example.com/path /
Jason
1
[email protected] funciona sin problemas en Chrome, pero provoca una oferta de seguridad en Firefox. cerrar sesión: [email protected] la dosis no convierte a Firefox en una opción de seguridad. Ninguna de las dos URL funciona en IE8: /
Thor A. Pedersen
46

La respuesta simple es que no puede cerrar sesión de forma confiable en la autenticación http.

La respuesta larga:
Http-auth (como el resto de la especificación HTTP) no tiene estado. Por lo tanto, estar "conectado" o "desconectado" no es realmente un concepto que tenga sentido. La mejor manera de verlo es preguntando, para cada solicitud HTTP (y recuerde que una carga de página suele ser varias solicitudes), "¿se le permite hacer lo que está solicitando?". El servidor ve cada solicitud como nueva y no relacionada con ninguna solicitud anterior.

Los navegadores han optado por recordar las credenciales que les dices en el primer 401 y volver a enviarlas sin el permiso explícito del usuario en solicitudes posteriores. Este es un intento de darle al usuario el modelo "conectado / desconectado" que esperan, pero es puramente un error. Es el navegador que simula esta persistencia de estado. El servidor web lo desconoce por completo.

Entonces, "cerrar sesión", en el contexto de http-auth, es puramente una simulación proporcionada por el navegador y, por lo tanto, está fuera de la autoridad del servidor.

Sí, hay kludges. Pero rompen el RESTfulness (si eso es de valor para usted) y no son confiables.

Si necesita absolutamente un modelo de inicio / cierre de sesión para la autenticación de su sitio, la mejor opción es una cookie de seguimiento, con la persistencia del estado almacenado en el servidor de alguna manera (mysql, sqlite, flatfile, etc.). Esto requerirá que todas las solicitudes sean evaluadas, por ejemplo, con PHP.

Jonathan Hanson
fuente
26

Solución alterna

Puedes hacer esto usando Javascript:

<html><head>
<script type="text/javascript">
function logout() {
    var xmlhttp;
    if (window.XMLHttpRequest) {
          xmlhttp = new XMLHttpRequest();
    }
    // code for IE
    else if (window.ActiveXObject) {
      xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    if (window.ActiveXObject) {
      // IE clear HTTP Authentication
      document.execCommand("ClearAuthenticationCache");
      window.location.href='/where/to/redirect';
    } else {
        xmlhttp.open("GET", '/path/that/will/return/200/OK', true, "logout", "logout");
        xmlhttp.send("");
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4) {window.location.href='/where/to/redirect';}
        }


    }


    return false;
}
</script>
</head>
<body>
<a href="#" onclick="logout();">Log out</a>
</body>
</html>

Lo que se hace arriba es:

  • para IE : simplemente borre el caché de autenticación y redirija a alguna parte

  • para otros navegadores : envíe una solicitud XMLHttpRequest detrás de escena con el nombre de usuario y la contraseña de 'cerrar sesión'. Necesitamos enviarlo a una ruta que devuelva 200 OK a esa solicitud (es decir, no debería requerir autenticación HTTP).

Reemplace '/where/to/redirect'con alguna ruta para redirigir después de cerrar sesión y reemplace '/path/that/will/return/200/OK'con alguna ruta en su sitio que arroje 200 OK.

Anton Mochalin
fuente
55
Es algo así como una solución para iniciar sesión como otro usuario. Pero esto realmente funciona y merece más crédito.
Charlie Rudenstål
2
Creo que esta es la mejor respuesta. Como se indica en esta respuesta a una pregunta similar, puede haber alguna ventaja al aleatorizar la contraseña.
zelanix
2
Esto era lo que quería: funcionaba en todos los navegadores sin problemas. Mantuve la página de "cerrar sesión" que heredé intacta. No necesariamente quería usar JS (tal vez irracionalmente), pero todas las otras respuestas tenían problemas de navegador cruzado y esto funcionó a la perfección.
dgig
No puedo hacer que esto funcione como se explica. Cuando volví al área segura, el navegador se autentica nuevamente con el envío de las últimas credenciales válidas utilizadas en el encabezado. Sin embargo, con un pequeño cambio funcionó para mí. Cambié la respuesta 200 OK con un encabezado con el mismo Reino del área protegida, pero aceptando solo un usuario / pase "logout: logout". De esta manera, el usuario inició sesión con este usuario "cerrar sesión", y este es el usuario que vuelve a intentarlo cuando regresa al área protegida. El área segura rechaza este usuario / pase, por lo que el usuario puede cambiar sus credenciales.
jonaguera
2
Esto no funciona como se explica. Probado en Chrome 40 y Firefox 35.
funforums
13

Solución alternativa (no es una solución limpia, agradable (¡o incluso funciona! Ver comentarios)):

Desactiva sus credenciales una vez.

Puede mover su lógica de autenticación HTTP a PHP enviando los encabezados apropiados (si no ha iniciado sesión):

Header('WWW-Authenticate: Basic realm="protected area"');
Header('HTTP/1.0 401 Unauthorized');

Y analizando la entrada con:

$_SERVER['PHP_AUTH_USER'] // httpauth-user
$_SERVER['PHP_AUTH_PW']   // httpauth-password

Así que deshabilitar sus credenciales una vez debería ser trivial.

Karsten
fuente
18
El problema con esta solución es que: Dejas que IE sepa que las credenciales no están bien. Muestra un cuadro de diálogo de inicio de sesión con campos vacíos (que no muestran valores almacenados en el administrador de contraseñas). Pero cuando hace clic en cancelar y actualizar la página, envía las credenciales almacenadas y, por lo tanto, inicia sesión nuevamente.
Josef Sábl
Votado a favor; Como comentó Josef Sable, esto no resuelve el problema en cuestión.
Chris Wesseling
7

Cerrar sesión de autenticación básica HTTP en dos pasos

Digamos que tengo un dominio de autenticación básica HTTP llamado "protegido por contraseña", y Bob está conectado. Para cerrar sesión, hago 2 solicitudes AJAX:

  1. Acceder al script / logout_step1. Agrega un usuario temporal aleatorio a .htusers y responde con su nombre de usuario y contraseña.
  2. Acceda al script / logout_step2 autenticado con el nombre de usuario y contraseña temporales del usuario . El script elimina el usuario temporal y agrega este encabezado en la respuesta:WWW-Authenticate: Basic realm="Password protected"

En este punto, el navegador olvidó las credenciales de Bob.

Vlad GURDIGA
fuente
1
¡Guauu! Esto realmente merece un +1 por pura inventiva, incluso si es algo completamente loco.
Andy Triggs
7

Mi solución al problema es la siguiente. Puede encontrar la función http_digest_parse, $realmy $usersen el segundo ejemplo de esta página: http://php.net/manual/en/features.http-auth.php .

session_start();

function LogOut() {
  session_destroy();
  session_unset($_SESSION['session_id']);
  session_unset($_SESSION['logged']);

  header("Location: /", TRUE, 301);   
}

function Login(){

  global $realm;

  if (empty($_SESSION['session_id'])) {
    session_regenerate_id();
    $_SESSION['session_id'] = session_id();
  }

  if (!IsAuthenticated()) {  
    header('HTTP/1.1 401 Unauthorized');
    header('WWW-Authenticate: Digest realm="'.$realm.
   '",qop="auth",nonce="'.$_SESSION['session_id'].'",opaque="'.md5($realm).'"');
    $_SESSION['logged'] = False;
    die('Access denied.');
  }
  $_SESSION['logged'] = True;  
}

function IsAuthenticated(){
  global $realm;
  global $users;


  if  (empty($_SERVER['PHP_AUTH_DIGEST']))
      return False;

  // check PHP_AUTH_DIGEST
  if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
     !isset($users[$data['username']]))
     return False;// invalid username


  $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
  $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);

  // Give session id instead of data['nonce']
  $valid_response =   md5($A1.':'.$_SESSION['session_id'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

  if ($data['response'] != $valid_response)
    return False;

  return True;
}
Pie86
fuente
4

Por lo general, una vez que un navegador ha pedido al usuario las credenciales y las ha proporcionado a un sitio web en particular, continuará haciéndolo sin más indicaciones. A diferencia de las diversas formas en que puede borrar las cookies en el lado del cliente, no conozco una forma similar de pedirle al navegador que olvide las credenciales de autenticación proporcionadas.

Greg Hewgill
fuente
Creo que hay una opción para eliminar sesiones autenticadas cuando selecciona "Eliminar datos privados" en Firefox
Kristian J.
1
También la extensión de la barra de herramientas de desarrollador web para Firefox ofrece la función de eliminar las autenticaciones HTTP. Pero esto está fuera de discusión ya que realmente no podemos pedir a nuestros usuarios que descarguen extensiones FF o ejecuten comandos crípticos del navegador :-)
Josef Sábl 03 de
2
La forma predeterminada de cierre de sesión de Firefox de la autenticación HTTP está disponible en "Herramientas"> "Borrar historial reciente ...", como la casilla de verificación "Inicios de sesión activos". Esto no es intuitivo ni le permite cerrar sesión solo en un dominio, siempre cierra sesión en cada página.
aef
2

Trac, de manera predeterminada, también utiliza la autenticación HTTP. Cerrar sesión no funciona y no se puede solucionar:

  • Este es un problema con el esquema de autenticación HTTP en sí mismo, y no hay nada que podamos hacer en Trac para solucionarlo correctamente.
  • Actualmente no existe una solución alternativa (JavaScript u otra) que funcione con todos los principales navegadores.

De: http://trac.edgewall.org/ticket/791#comment:103

Parece que no hay una respuesta funcional a la pregunta, ese problema se informó hace siete años y tiene mucho sentido: HTTP no tiene estado. Una solicitud se realiza con credenciales de autenticación o no. Pero eso es cuestión de que el cliente envíe la solicitud, no del servidor que la recibe. El servidor solo puede decir si un URI de solicitud necesita autorización o no.

hakre
fuente
2

Necesitaba restablecer la autorización .htaccess, así que usé esto:

<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
}
?>

Lo encontré aquí: http://php.net/manual/en/features.http-auth.php

Imagínate.

Varias soluciones residen en esa página e incluso se observa en la parte inferior: Lynx, no borra la autenticación como otros navegadores;)

Lo probé en mis navegadores instalados y una vez cerrado, parece que cada navegador requiere constantemente una nueva autorización al volver a ingresar.

Dooley
fuente
Esto no parece funcionar, recibo el texto de cancelación sin ningún cuadro de inicio de sesión emergente.
Michael
Resulta que enviar el mensaje WWW-Authenticateestaba causando el problema, deshacerse de eso me desconectó automáticamente.
Michael
Y, por el contrario, parece que NO enviar el WWW-Authenticatemensaje mientras soluciona el problema en un navegador (Chrome) hace que otro navegador (Firefox) recuerde las credenciales y las envíe en la siguiente solicitud, lo que resulta en un reinicio de sesión automático. Argh!
Michael
Luego mira UA y hacer una u otra parece una solución
Lennart Rolland
2

Esta podría no ser la solución que se buscaba, pero la resolví así. Tengo 2 scripts para el proceso de cierre de sesión.

logout.php

<?php
header("Location: http://[email protected]/log.php");
?>

log.php

<?php
header("location: https://google.com");
?>

De esta manera no recibo una advertencia y mi sesión finaliza

Kevin
fuente
1
¡Esta fue la única solución que realmente funcionó para mí! Probado en Firefox 37 y Chromium 41
zesaver
1

AFAIK, no hay una forma limpia de implementar una función de "cierre de sesión" cuando se usa la autenticación htaccess (es decir, basada en HTTP).

Esto se debe a que dicha autenticación utiliza el código de error HTTP '401' para indicarle al navegador que se requieren credenciales, momento en el cual el navegador solicita al usuario los detalles. A partir de ese momento, hasta que se cierre el navegador, siempre enviará las credenciales sin más indicaciones.

Alnitak
fuente
1

La mejor solución que encontré hasta ahora es (es una especie de pseudocódigo, $isLoggedInes una pseudo variable para http auth):

En el momento del "cierre de sesión", simplemente almacene cierta información en la sesión que indique que el usuario está realmente desconectado.

function logout()
{
  //$isLoggedIn = false; //This does not work (point of this question)
  $_SESSION['logout'] = true;
}

En el lugar donde verifico la autenticación, amplío la condición:

function isLoggedIn()
{
  return $isLoggedIn && !$_SESSION['logout'];
}

La sesión está algo vinculada al estado de la autenticación http, por lo que el usuario permanece desconectado mientras mantenga abierto el navegador y mientras la autenticación http persista en el navegador.

Josef Sábl
fuente
44
Mientras que la autenticación básica http es RESTful, las sesiones no lo son.
Deamon
1

Tal vez me estoy perdiendo el punto.

La forma más confiable que he encontrado para finalizar la autenticación HTTP es cerrar el navegador y todas las ventanas del navegador. Puede cerrar una ventana del navegador usando Javascript, pero no creo que pueda cerrar todas las ventanas del navegador.

Toby Allen
fuente
Para algunos navegadores no se cerrará una ventana si es la única pestaña abierta, por lo que el punto es discutible realmente
escape
Hace muchos años tuve la tarea de implementar el botón de cerrar sesión sin cerrar la ventana :-) Pero tal vez no se detendrían en "no cerrar la ventana". Pero bueno, esta es una solución simple que podría funcionar para alguien y la perdí en ese entonces para ser honesto.
Josef Sábl
1

La única forma efectiva que he encontrado para eliminar las credenciales PHP_AUTH_DIGESTo PHP_AUTH_USERAND PHP_AUTH_PWes llamar al encabezado HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
CIRCULO
fuente
0

Mientras que los otros tienen razón al decir que es imposible cerrar sesión desde la autenticación básica http, hay formas de implementar la autenticación que se comportan de manera similar. Un enfoque obvio es usar auth_memcookie . Si realmente desea implementar la autenticación HTTP básica (es decir, usar los cuadros de diálogo del navegador para iniciar sesión en un lugar que no sea un formulario HTTP) utilizando esto, simplemente configure la autenticación en un directorio protegido .htaccess separado que contenga un script PHP que redirige de nuevo a donde vino el usuario creando la sesión de memcache.

symcbean
fuente
0

Aquí hay muchas respuestas excelentes, complejas. En mi caso particular, encontré una solución limpia y simple para cerrar sesión. Todavía tengo que probar en Edge. En mi página en la que he iniciado sesión, he colocado un enlace de cierre de sesión similar a este:

<a href="https://MyDomainHere.net/logout.html">logout</a>

Y en la cabecera de esa página logout.html (que también está protegida por .htaccess) tengo una actualización de página similar a esta:

<meta http-equiv="Refresh" content="0; url=https://logout:[email protected]/" />

Donde dejaría las palabras "cerrar sesión" para borrar el nombre de usuario y la contraseña almacenados en caché para el sitio.

Admitiré que si se necesita iniciar sesión directamente en varias páginas desde el principio, cada uno de esos puntos de entrada necesitaría su propia página correspondiente logout.html. De lo contrario, podría centralizar el cierre de sesión introduciendo un paso de gatekeeper adicional en el proceso antes de la solicitud de inicio de sesión real, que requiere la entrada de una frase para llegar a un destino de inicio de sesión.

John Wayne
fuente
1
al avanzar, esto funciona, cierra la sesión, pero el historial del navegador aún puede restablecer la sesión.
johnwayne