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