Ir para o conteúdo

Criar um sistema de recuperação de password em PHP

Depois de ter um sistema de login em PHP a funcionar, pode tornar-se útil fornecer aos utilizadores alguns extras. Por exemplo, no caso de um utilizador se esquecer da sua password, é preciso ter um mecanismo que permita ao utilizador redefinir a sua password.

Neste artigo, vamos ver como criar um sistema de recuperação de password simples, para que o utilizador possa alterar a sua password para uma nova.

Funcionamento

Neste caso, para simplificar, vamos admitir que temos na tabela de utilizadores duas informações: e-mail e password codificada (em sha1). Quando o utilizador pede uma recuperação de password, verificamos se esse e-mail existe. Se o e-mail existir, então trata-se de um pedido válido.

Temos então de enviar um link único ao utilizador, para o seu e-mail, para que possa alterar a sua password sem ter de introduzir uma password (visto não a saber). Por exemplo:

http://exameple.net/recuperar.php?utilizador=tobias@mail.com

No entanto, este link não é suficiente. Um atacante poderia simplesmente substituir o endereço de e-mail neste link e assim alterar a password de qualquer conta cujo e-mail fosse conhecido. Assim, precisamos de incluir no link algo aleatório, de modo a permitir que esse link seja usado apenas uma vez. Por exemplo:

http://exameple.net/recuperar.php?utilizador=tobias@mail.com&confirmacao=be254882180cf554fb2a7e05c62ed5a13088500e

O argumento confirmacao muda a cada pedido de password, e assim, se este par utilizador/confirmacao existir, ele é único, e temos a certeza que o utilizador é o único que tem acesso a este endereço (assumindo que o e-mail utilizado é um canal seguro, mas isso está fora do escopo do artigo). Para guardar este par de valores, precisamos ainda de uma tabela dedicada para o efeito.

Percebida a teoria, vamos à prática!

Código SQL e PHP

Antes de mais, é necessário criar a tabela que irá conter os pedidos de recuperação de passwords.

CREATE TABLE recuperacao (
  utilizador  varchar(255) NOT NULL,
  confirmacao varchar(40) NOT NULL,
  KEY(utilizador, confirmacao)
)

A chave é composta pelos dois campos, pois desta forma torna-se mais rápido procurar por ambas as informações na base de dados, e não impõe restrições ao utilizador de fazer mais de um pedido de recuperação (por exemplo, no caso do primeiro e-mail ter sido eliminado por engano ou não ter chegado à caixa de e-mail do utilizador).

Código PHP

Vamos criar duas páginas: uma para pedidos de recuperação e outra para alterar a password. Estas páginas terão os nomes perdipassword.php e recuperar.php, respetivamente.

perdipassword.php
<h1>Perdi a password</h1>
<?php
  if( !empty($_POST) ){
    // processar o pedido
    mysql_connect('localhost', 'root', '');  // ligar à base de dados
    mysql_select_db('test');  // escolher a base de dados pretendida

    $user = mysql_real_escape_string($_POST['email']);
    $q = mysql_query("SELECT * FROM utilizadores WHERE email = '$user'");

    if( mysql_num_rows($q) == 1 ){
      // o utilizador existe, vamos gerar um link único e enviá-lo para o e-mail

      // gerar a chave
      // exemplo adaptado de http://snipplr.com/view/20236/
      $chave = sha1(uniqid( mt_rand(), true));

      // guardar este par de valores na tabela para confirmar mais tarde
      $conf = mysql_query("INSERT INTO recuperacao VALUES ('$user', '$chave')");
      echo "INSERT INTO recuperacao VALUES ('$user', '$chave')";

      if( mysql_affected_rows() == 1 ){

        $link = "http://example.net/recuperar.php?utilizador=$user&confirmacao=$chave";

        if( mail($user, 'Recuperação de password', 'Olá '.$user.', visite este link '.$link) ){
          echo '<p>Foi enviado um e-mail para o seu endereço, onde poderá encontrar um link único para alterar a sua password</p>';

        } else {
          echo '<p>Houve um erro ao enviar o email (o servidor suporta a função mail?)</p>';

        }

        // Apenas para testar o link, no caso do e-mail falhar
        echo '<p>Link: '.$link.' (apresentado apenas para testes; nunca expor a público!)</p>';

      } else {
        echo '<p>Não foi possível gerar o endereço único</p>';

      }
    } else {
      echo '<p>Esse utilizador não existe</p>';
    }
  } else {
    // mostrar formulário de recuperação
?>
<form method="post">
  <label for="email">E-mail:</label>
  <input type="text" name="email" id="email" />
  <input type="submit" value="Recuperar" />
</form>
<?php
  }
?>
recuperar.php
<h1>Alterar password</h1>
<?php
  if( empty($_GET['utilizador']) || empty($_GET['confirmacao']) )
    die('<p>Não é possível alterar a password: dados em falta</p>');

  mysql_connect('localhost', 'root', '');  // ligar à base de dados
  mysql_select_db('test');  // escolher a base de dados pretendida

  $user = mysql_real_escape_string($_GET['utilizador']);
  $hash = mysql_real_escape_string($_GET['confirmacao']);

  $q = mysql_query("SELECT COUNT(*) FROM recuperacao WHERE utilizador = '$user' AND confirmacao = '$hash'");

  if( mysql_result($q, 0, 0) == "1" ){
    // os dados estão corretos: eliminar o pedido e permitir alterar a password
    mysql_query("DELETE FROM recuperacao WHERE utilizador = '$user' AND confirmacao = '$hash'");

    echo 'Sucesso! (Mostrar formulário de alteração de password aqui)';   

  } else {
    echo '<p>Não é possível alterar a password: dados incorretos</p>';

  }

?>

Com este sistema, é possível agora que os utilizadores possam recuperar as suas passwords. Não se esqueçam de perceber o código, e alterar as informações do MySQL e de incluir o vosso formulário de recuperação de password. Reparem ainda que o link é único, e que se o tentarem utilizar novamente, não é possível alterar a password, dando o erro de os dados estarem incorretos.

Vantagens e desvantagens deste sistema

Em termos de vantagens, podemos enumerar algumas:

  • O link é único para cada pedido e apenas acessível pelo utilizador.
  • É forçada uma alteração de password, de modo a que o utilizador tenha conhecimento de que a sua conta foi alterada.

Mas também existem desvantagens:

  • Um atacante pode gerar vários pedidos e tentar adivinhar o código de confirmação. No entanto, devido ao uso do algoritmo SHA1, essa é uma tarefa que se torna difícil de fazer em tempo útil. Em todo o caso, podemos limitar o número de pedidos de password para cada utilizador.