Logout de autenticação HTTP via PHP

151

Qual é a maneira correta de sair da pasta protegida por autenticação HTTP?

Existem soluções alternativas que podem conseguir isso, mas são potencialmente perigosas porque podem ser de buggy ou não funcionam em determinadas situações / navegadores. É por isso que estou procurando uma solução correta e limpa.

Josef Sábl
fonte
Especifique a finalidade do seu logout. Deve ser um logout forçado (desativação do usuário)? Função de logout simples para o usuário? Algo mais?
226 Karsten
6
Não entendo por que isso importa, mas os dois casos são: desativação com base nas condições internas do aplicativo e no botão de logout típico. Por favor, explique por que é importante, vou editá-lo diretamente na pergunta.
Josef Sábl
2
A "solução correta e limpa" seria os navegadores com seu próprio botão de logout que, ao serem clicados, farão com que o navegador pare de enviar os cabeçalhos de Auth ... Pode-se sonhar, certo?
DanMan
1
A barra de ferramentas do desenvolvedor da Web possui esse "botão".
Josef Sábl
O que Josef disse: barra de ferramentas do desenvolvedor web para Firefox ->Miscellaneous -> Clear Private Data -> HTTP Authentication
Yarin

Respostas:

103

Mu. Não existe uma maneira correta , nem mesmo uma consistente entre os navegadores.

Este é um problema que vem da especificação HTTP (seção 15.6):

Clientes HTTP existentes e agentes do usuário geralmente retêm informações de autenticação indefinidamente. HTTP / 1.1. não fornece um método para um servidor direcionar clientes a descartar essas credenciais em cache.

Por outro lado, a seção 10.4.2 diz:

Se a solicitação já incluía credenciais de autorização, a resposta 401 indica que a autorização foi recusada para essas credenciais. Se a resposta 401 contém o mesmo desafio que a resposta anterior, e o agente do usuário já tentou a autenticação pelo menos uma vez, então o usuário DEVE ser apresentada a entidade que foi dada na resposta, já que essa entidade pode incluir informações de diagnóstico relevantes.

Em outras palavras, você poderá mostrar a caixa de login novamente (como diz @Karsten ), mas o navegador não precisa atender à sua solicitação - portanto, não dependa muito desse recurso.

Piskvor saiu do prédio
fonte
9
Este é um erro no RFC. W3C com preguiça de corrigir. Tão triste.
Erik Aronesty
Como @ Jonathan Hanson sugeriu abaixo , você pode usar um cookie de rastreamento junto com a autenticação HTTP. Este é o melhor método para mim.
machineaddict
61

Método que funciona bem no Safari. Também funciona no Firefox e Opera, mas com um aviso.

Location: http://[email protected]/

Isso indica ao navegador para abrir o URL com o novo nome de usuário, substituindo o anterior.

Kornel
fonte
14
De acordo com a RFC 3986 (URI: sintaxe genérica), seção 3.2.1. (Informações do usuário) o uso de user:password@hostfoi preterido. Usar apenas http://[email protected]/não é e deve funcionar na maioria dos casos.
aef
1
@andho: sim, é um redirecionamento. Você deve usá-lo com o status 302.
Kornel
1
Aparentemente, um link simples para [email protected] também funciona (um link "desconectar" para este URL) em vez de um redirecionamento http no PHP ... alguma desvantagem nisso?
Moala
4
Cuidado: envio de formulário usando caminho relativo pode falhar quando é feito depois de um re-login (login com o logout do prompt), porque o endereço ainda seria [email protected]/path e não yourserver.example.com/path /
Jason
1
[email protected] funciona sem problemas no Chrome, mas solicita uma solicitação de segurança no Firefox. logout: [email protected] não faz do Firefox uma solicitação de segurança. Nenhum dos dois URLs funciona no IE8: /
Thor A. Pedersen
46

A resposta simples é que você não pode efetuar logoff confiável da autenticação http.

A resposta longa:
Http-auth (como o restante da especificação HTTP) deve ser sem estado. Portanto, estar "logado" ou "desconectado" não é realmente um conceito que faça sentido. A melhor maneira de ver isso é perguntando, para cada solicitação HTTP (e lembre-se de que o carregamento de uma página é geralmente várias solicitações), "você tem permissão para fazer o que está solicitando?". O servidor vê cada solicitação como nova e não relacionada a nenhuma solicitação anterior.

Os navegadores optaram por lembrar as credenciais que você lhes diz no primeiro 401 e reenviá-las sem a permissão explícita do usuário em solicitações subsequentes. Esta é uma tentativa de fornecer ao usuário o modelo "logado / desconectado" que eles esperam, mas é puramente um clamor. É o navegador que simula essa persistência de estado. O servidor web desconhece completamente isso.

Portanto, "fazer logoff", no contexto de http-auth, é puramente uma simulação fornecida pelo navegador e, portanto, fora da autoridade do servidor.

Sim, existem julgamentos. Mas eles quebram o RESTfulness (se isso é de valor para você) e não são confiáveis.

Se você precisar absolutamente de um modelo de logon / logoff para a autenticação do site, a melhor opção é um cookie de rastreamento, com a persistência do estado armazenado no servidor de alguma maneira (mysql, sqlite, flatfile etc.). Isso exigirá que todas as solicitações sejam avaliadas, por exemplo, com PHP.

Jonathan Hanson
fonte
26

Gambiarra

Você pode fazer isso 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>

O que é feito acima é:

  • para IE - limpe o cache de autenticação e redirecione para algum lugar

  • para outros navegadores - envie um XMLHttpRequest nos bastidores com o nome e a senha de logout 'logout'. Precisamos enviá-lo para algum caminho que retorne 200 OK a essa solicitação (ou seja, não deve exigir autenticação HTTP).

Substitua '/where/to/redirect'por algum caminho para redirecionar após o logout e substitua '/path/that/will/return/200/OK'por algum caminho no seu site que retornará 200 OK.

Anton Mochalin
fonte
5
É uma solução alternativa fazer login como outro usuário. Mas isso realmente funciona e merece mais crédito.
Charlie Rudenstål
2
Eu acho que essa é a melhor resposta. Conforme indicado nesta resposta a uma pergunta semelhante, pode haver alguma vantagem em randomizar a senha.
zelanix
2
Era isso que eu queria - funcionava em todos os navegadores sem problemas. Manteve a página de "logout" que herdei intacta. Eu não queria necessariamente usar JS (talvez irracionalmente), mas as outras respostas tiveram problemas entre navegadores e isso funcionou perfeitamente.
Dgig
Não posso fazer isso funcionar da maneira que é explicada. Quando voltei para a área segura, o navegador se autentica novamente enviando as últimas credenciais válidas usadas no cabeçalho. No entanto, com uma pequena mudança, funcionou para mim. Alterei a resposta 200 OK com um cabeçalho com o mesmo domínio da área segura, mas aceitando apenas um usuário / passe "logout: logout". Dessa forma, o usuário efetuou login com esse usuário "logout" e é o usuário que tenta novamente quando volta para a área segura. A área segura rejeita esse usuário / passe, para que o usuário possa alterar suas credenciais.
Jonaguera
2
Isso não funciona como é explicado. Testado no Chrome 40 e Firefox 35.
funforums 26/02
13

Solução alternativa (não uma solução limpa, agradável (ou mesmo funcionando! Veja comentários)):

Desative suas credenciais uma vez.

Você pode mover sua lógica de autenticação HTTP para PHP enviando os cabeçalhos apropriados (se não estiver logado):

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

E analisando a entrada com:

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

Portanto, desabilitar suas credenciais uma vez deve ser trivial.

Karsten
fonte
18
O problema com esta solução é: Você deixa o IE saber que as credenciais não estão OK. Ele exibe a caixa de diálogo de login com campos vazios (não mostrando os valores armazenados no gerenciador de senhas). Mas quando você clica em cancelar e atualizar a página, ela envia credenciais armazenadas, efetuando login novamente.
Josef Sábl
Votado; Como Josef Sable comentou, isso não resolve o problema em questão.
22812 Chris Wesseling
7

Efetue logout do HTTP Basic Auth em duas etapas

Digamos que eu tenha um domínio HTTP Basic Auth chamado “Password protected” e Bob esteja logado. Para fazer logout, faço 2 solicitações AJAX:

  1. Acesse script / logout_step1. Ele adiciona um usuário temporário aleatório ao .htusers e responde com seu login e senha.
  2. Acesse o script / logout_step2 autenticado com o login e a senha do usuário temporário . O script exclui o usuário temporário e adiciona este cabeçalho na resposta:WWW-Authenticate: Basic realm="Password protected"

Nesse ponto, o navegador esqueceu as credenciais de Bob.

Vlad GURDIGA
fonte
1
Uau! Isso realmente merece um +1 por pura inventividade, mesmo que seja uma coisa completamente louca de se fazer.
Andy Triggs
7

Minha solução para o problema é a seguinte. Você pode encontrar a função http_digest_parse, $realme $usersno segundo exemplo desta 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
fonte
4

Normalmente, depois que um navegador solicita credenciais ao usuário e as fornece a um site específico, ele continuará fazendo isso sem avisar. Diferente das várias maneiras pelas quais você pode limpar os cookies no lado do cliente, não conheço uma maneira semelhante de solicitar ao navegador que esqueça as credenciais de autenticação fornecidas.

Greg Hewgill
fonte
Eu creio que há uma opção para excluir sessões autenticadas quando selecciona "dados privados Excluir" no Firefox
Kristian J.
1
Além disso, a extensão Web Developer Toolbar para Firefox oferece um recurso para excluir as autenticações HTTP. Mas isso está fora de questão como nós realmente não podemos pedir aos nossos usuários a baixar extensões FF ou executar comandos do navegador enigmáticas :-)
Josef SABL
2
A maneira padrão do Firefox de fazer logoff da autenticação HTTP está disponível em "Ferramentas"> "Limpar histórico recente ...", como a caixa de seleção "Logins ativos". Isso não é intuitivo, nem permite que você efetue logout apenas de um domínio, mas sempre faça logout de todas as páginas.
aef
2

Trac - por padrão - também usa autenticação HTTP. Logout não funciona e não pode ser corrigido:

  • Esse é um problema do próprio esquema de autenticação HTTP e não há nada que possamos fazer no Trac para corrigi-lo corretamente.
  • Atualmente, não há solução alternativa (JavaScript ou outra) que funcione com todos os principais navegadores.

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

Parece que não há resposta para a pergunta, esse problema foi relatado há sete anos e faz todo o sentido: o HTTP é sem estado. Uma solicitação é feita com credenciais de autenticação ou não. Mas isso é uma questão de o cliente enviar a solicitação, não o servidor que a recebe. O servidor pode apenas dizer se um URI de solicitação precisa de autorização ou não.

hakre
fonte
2

Eu precisava redefinir a autorização .htaccess, então usei isso:

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

Encontre-o aqui: http://php.net/manual/en/features.http-auth.php

Vai saber.

Várias soluções residem nessa página e até aparecem na parte inferior: Lynx, não limpa a autenticação como outros navegadores;)

Eu testei nos meus navegadores instalados e, uma vez fechados, cada navegador parece exigir consistentemente uma nova tentativa de reentrada.

Dooley
fonte
Parece que não está funcionando, estou recebendo o texto de cancelamento sem nenhuma caixa de login pop-up.
Michael
Acontece que o envio do WWW-Authenticateproblema estava causando o problema e a remoção automática do meu logout.
Michael
E, inversamente, parece que NÃO enviar o WWW-Authenticateproblema durante a correção do problema em um navegador (Chrome) faz com que outro navegador (Firefox) lembre das credenciais e as envie na próxima solicitação, resultando em um novo login automático! Argh!
Michael
Então olha para UA e fazer um ou outro parece ser uma solução
Lennart Rolland
2

Esta pode não ser a solução que foi procurada, mas eu a resolvi assim. Eu tenho 2 scripts para o processo de logout.

logout.php

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

log.php

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

Dessa forma, não recebo um aviso e minha sessão é encerrada

Kevin
fonte
1
Esta foi a única solução realmente funcionou para mim! Testado no Firefox 37 e Chromium 41
zesaver
1

AFAIK, não há uma maneira limpa de implementar uma função de "logout" ao usar a autenticação htaccess (isto é, baseada em HTTP).

Isso ocorre porque essa autenticação usa o código de erro HTTP '401' para informar ao navegador que credenciais são necessárias; nesse momento, o navegador solicita ao usuário os detalhes. A partir de então, até o navegador ser fechado, ele sempre enviará as credenciais sem aviso prévio.

Alnitak
fonte
1

A melhor solução que encontrei até agora é (é uma espécie de pseudo-código, o $isLoggedIn pseudo-variável is para http auth):

No momento do "logout", basta armazenar algumas informações na sessão dizendo que o usuário está realmente desconectado.

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

No local em que verifico a autenticação, expanda a condição:

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

A sessão está um pouco vinculada ao estado de autenticação http, para que o usuário permaneça desconectado enquanto mantiver o navegador aberto e enquanto a autenticação http persistir no navegador.

Josef Sábl
fonte
4
Enquanto a autenticação básica http é RESTful, as sessões não são.
deamon
1

Talvez eu esteja perdendo o objetivo.

A maneira mais confiável que encontrei para encerrar a autenticação HTTP é fechar o navegador e todas as janelas do navegador. Você pode fechar uma janela do navegador usando Javascript, mas acho que não pode fechar todas as janelas do navegador.

Toby Allen
fonte
fyi alguns navegadores não vai fechar uma janela se é a única aberta guia, então o ponto é discutível realmente
scape
Há muitos anos, tive a tarefa de implementar o botão de logout sem fechar a janela :-) Mas talvez eles não insistissem em "não fechar a janela". Mas, ei, essa é uma solução simples que pode funcionar para alguém e eu senti falta dela naquela época para ser honesto.
Josef Sábl 19/10/19
1

A única maneira eficaz que encontrei de eliminar as credenciais PHP_AUTH_DIGESTou PHP_AUTH_USERAND PHP_AUTH_PWé chamar o cabeçalho HTTP/1.1 401 Unauthorized.

function clear_admin_access(){
    header('HTTP/1.1 401 Unauthorized');
    die('Admin access turned off');
}
CÍRCULO
fonte
0

Enquanto os outros estão certos ao dizer que é impossível fazer logout da autenticação http básica, existem maneiras de implementar a autenticação que se comporta de maneira semelhante. Uma abordagem óbvia é usar auth_memcookie . Se você realmente deseja implementar a autenticação HTTP básica (por exemplo, use as caixas de diálogo do navegador para fazer login em um formulário HTTP) usando isso - basta definir a autenticação para um diretório protegido .htaccess separado contendo um script PHP que redireciona de volta para onde o usuário veio depois criando a sessão do memcache.

symcbean
fonte
0

Há muitas respostas ótimas - complexas - aqui. No meu caso particular, encontrei uma correção limpa e simples para o logout. Ainda tenho que testar no Edge. Na minha página em que efetuei login, coloquei um link de logoff semelhante a este:

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

E na cabeça dessa página logout.html (que também é protegida pelo .htaccess), tenho uma atualização de página semelhante a esta:

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

Onde você deixaria as palavras "logout" no lugar para limpar o nome de usuário e a senha em cache do site.

Admito que, se várias páginas precisassem poder fazer login diretamente desde o início, cada um desses pontos de entrada precisaria de sua própria página logout.html correspondente. Caso contrário, você poderá centralizar o logout, introduzindo uma etapa adicional do gatekeeper no processo antes do prompt de login real, exigindo a entrada de uma frase para alcançar o destino do logon.

johnwayne
fonte
1
ao avançar, isso funciona, ele efetua logout, mas o histórico de retorno do navegador ainda pode restabelecer a sessão.
precisa