Autenticação é o processo de verificar a identidade de um usuário, geralmente através de credenciais como e-mail e senha. Em aplicações web, após a autenticação, o sistema precisa manter o estado do usuário logado durante a navegação. Nesta aula, vamos explorar como implementar um sistema de login robusto em PHP, utilizando sessões, cookies, e técnicas de segurança para proteger contra ataques comuns.

Vamos começar com os conceitos básicos de sessão e login, depois avançaremos para recursos como 'lembrar-me', que mantém o usuário autenticado por mais tempo, e finalizaremos com boas práticas essenciais para produção.

Login e sessão

O login é o ponto de entrada para áreas restritas de uma aplicação. Em PHP, a forma mais comum de manter o usuário autenticado é através de sessões. Uma sessão é um mecanismo que armazena dados do usuário no servidor, associados a um identificador único (ID de sessão) que é enviado ao cliente via cookie. Quando o usuário faz login, validamos as credenciais e, se forem corretas, armazenamos dados como o ID do usuário na variável superglobal $_SESSION.

Para iniciar uma sessão, usamos session_start() no início do script. É importante que essa função seja chamada antes de qualquer saída HTML. Vamos ver um exemplo de script de login:

<?php
session_start();

// Simulando um banco de dados de usuários (em produção, use um banco real)
$usuarios = [
    ['id' => 1, 'email' => 'user@example.com', 'senha_hash' => password_hash('senha123', PASSWORD_DEFAULT)],
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = $_POST['email'] ?? '';
    $senha = $_POST['senha'] ?? '';

    // Buscar usuário pelo e-mail (em produção, use prepared statements)
    $usuario = null;
    foreach ($usuarios as $u) {
        if ($u['email'] === $email) {
            $usuario = $u;
            break;
        }
    }

    if ($usuario && password_verify($senha, $usuario['senha_hash'])) {
        // Regenerar ID da sessão para evitar fixação
        session_regenerate_id(true);
        $_SESSION['user_id'] = $usuario['id'];
        $_SESSION['logged_in'] = true;
        header('Location: dashboard.php');
        exit;
    } else {
        $erro = 'Credenciais inválidas.';
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <?php if (isset($erro)): ?>
        <p style="color: red;"><?= htmlspecialchars($erro) ?></p>
    <?php endif; ?>
    <form method="post">
        <label>E-mail: <input type="email" name="email" required></label><br>
        <label>Senha: <input type="password" name="senha" required></label><br>
        <button type="submit">Entrar</button>
    </form>
</body>
</html>

No exemplo acima, após verificar as credenciais, chamamos session_regenerate_id(true) para evitar ataques de fixação de sessão. Em seguida, armazenamos o ID do usuário na sessão. Em páginas protegidas, verificamos se $_SESSION['logged_in'] está definido e se o usuário existe.

Lembrar-me

A funcionalidade "lembrar-me" permite que o usuário permaneça autenticado por um período prolongado, mesmo após fechar o navegador. Isso é implementado com cookies de longa duração, mas armazenar o ID do usuário em um cookie é inseguro. A prática recomendada é usar tokens aleatórios armazenados em uma tabela no banco de dados, associados ao usuário e com data de expiração.

Vamos criar um fluxo básico: quando o usuário marca "lembrar-me", geramos um token aleatório, armazenamos um hash dele no banco, e enviamos o token em um cookie. Quando o usuário retorna sem sessão ativa, verificamos o cookie e autenticamos via token.

// No login, se 'lembrar' estiver marcado:
if (!empty($_POST['lembrar'])) {
    $token = bin2hex(random_bytes(32));
    $token_hash = hash('sha256', $token);
    // Salvar $token_hash e expiração no banco para o usuário
    // Exemplo: INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)
    setcookie('remember_token', $token, time() + 30 * 24 * 3600, '/', '', true, true); // 30 dias, HttpOnly, Secure
}

// No início de cada requisição, se não houver sessão:
if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember_token'])) {
    $token = $_COOKIE['remember_token'];
    $token_hash = hash('sha256', $token);
    // Buscar token no banco e verificar expiração
    // Se válido, regenerar sessão e logar usuário
}

É importante definir o cookie com as flags HttpOnly (para não ser acessível via JavaScript) e Secure (para ser enviado apenas via HTTPS). Além disso, o token deve ser invalidado após o uso (por exemplo, rotacionar o token a cada login).

Logout seguro

O logout deve encerrar a sessão e limpar os cookies de forma segura. Além de destruir a sessão, devemos invalidar os tokens de "lembrar-me" no banco e limpar o cookie.

// logout.php
session_start();

// Limpar cookie de lembrar-me
if (isset($_COOKIE['remember_token'])) {
    // Excluir token do banco
    $token_hash = hash('sha256', $_COOKIE['remember_token']);
    // DELETE FROM user_tokens WHERE token_hash = ?
    setcookie('remember_token', '', time() - 3600, '/', '', true, true);
}

// Destruir a sessão
$_SESSION = [];
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}
session_destroy();

header('Location: login.php');
exit;

Esse código limpa todos os dados da sessão, remove o cookie de sessão e o cookie de lembrar-me, e redireciona para a página de login. Sempre use header('Location: ...') após o logout para evitar que o usuário veja conteúdo protegido.

Boas práticas

Implementar autenticação exige atenção a várias práticas de segurança. Aqui estão as principais:

  • Senhas: Nunca armazene senhas em texto puro; use password_hash() e password_verify().
  • HTTPS: Use HTTPS em todo o site para proteger as credenciais e cookies.
  • Proteção contra CSRF: Em formulários de login e logout, use tokens CSRF para evitar requisições forjadas.
  • Limitar tentativas de login: Implemente bloqueio após várias tentativas falhas para evitar força bruta.
  • Validação de entrada: Sempre valide e filtre dados de entrada.
  • Regenerar ID de sessão: Ao fazer login, chame session_regenerate_id(true).
  • Expiração de sessão: Defina um tempo de expiração para a sessão e encerre após inatividade.
  • Logs de auditoria: Registre eventos de login e logout para monitoramento.

Além disso, considere usar bibliotecas ou frameworks como Laravel ou Symfony, que já possuem sistemas de autenticação robustos e testados.

Referências

Exercícios

  1. Implemente um script de login que verifica as credenciais usando password_verify() e inicia a sessão. Use um array de usuários em memória.
  2. ✓ Resposta:
    <?php
    session_start();
    $usuarios = [
        ['id' => 1, 'email' => 'user@example.com', 'senha_hash' => password_hash('senha123', PASSWORD_DEFAULT)],
    ];
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $email = $_POST['email'];
        $senha = $_POST['senha'];
        foreach ($usuarios as $u) {
            if ($u['email'] === $email && password_verify($senha, $u['senha_hash'])) {
                session_regenerate_id(true);
                $_SESSION['user_id'] = $u['id'];
                header('Location: dashboard.php');
                exit;
            }
        }
        echo 'Credenciais inválidas.';
    }
    ?>
    
  3. Adicione a funcionalidade "lembrar-me" ao script de login, gerando um token e armazenando no banco (simule com um array).
  4. ✓ Resposta:
    <?php
    session_start();
    $usuarios = []; // simulação
    $tokens = []; // armazena hash => user_id e expiração
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // ... valida credenciais ...
        if ($usuario_valido) {
            session_regenerate_id(true);
            $_SESSION['user_id'] = $id;
            if (isset($_POST['lembrar'])) {
                $token = bin2hex(random_bytes(32));
                $hash = hash('sha256', $token);
                $tokens[$hash] = ['user_id' => $id, 'expires' => time() + 30*24*3600];
                setcookie('remember_token', $token, $tokens[$hash]['expires'], '/', '', true, true);
            }
            header('Location: dashboard.php');
            exit;
        }
    }
    // Verificar cookie em requisições sem sessão
    if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember_token'])) {
        $hash = hash('sha256', $_COOKIE['remember_token']);
        if (isset($tokens[$hash]) && $tokens[$hash]['expires'] > time()) {
            $_SESSION['user_id'] = $tokens[$hash]['user_id'];
            session_regenerate_id(true);
        }
    }
    ?>
    
  5. Crie um script de logout que limpe a sessão e o cookie de lembrar-me, e destrua a sessão.
  6. ✓ Resposta:
    <?php
    session_start();
    if (isset($_COOKIE['remember_token'])) {
        $hash = hash('sha256', $_COOKIE['remember_token']);
        // deletar do banco
        setcookie('remember_token', '', time()-3600, '/', '', true, true);
    }
    $_SESSION = [];
    if (ini_get("session.use_cookies")) {
        $params = session_get_cookie_params();
        setcookie(session_name(), '', time()-42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
    }
    session_destroy();
    header('Location: login.php');
    exit;
    
  7. Implemente uma função para verificar se o usuário está logado em uma página protegida, redirecionando para login se não estiver.
  8. ✓ Resposta:
    function verificar_login() {
        session_start();
        if (!isset($_SESSION['user_id'])) {
            header('Location: login.php');
            exit;
        }
    }
    // No topo de cada página protegida:
    verificar_login();
    
  9. Adicione proteção CSRF ao formulário de login, gerando um token e validando-o no POST.
  10. ✓ Resposta:
    <?php
    session_start();
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
            die('Token CSRF inválido.');
        }
        // processar login
    }
    ?>
    <form method="post">
        <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
        <!-- outros campos -->
    </form>
    

Boas práticas e observações finais

Além do que vimos, é fundamental manter o código organizado e testado. Considere usar um sistema de gerenciamento de dependências (Composer) e bibliotecas como defuse/php-encryption para criptografia, se necessário. Lembre-se de que a segurança é um processo contínuo: mantenha o PHP e as bibliotecas atualizadas, monitore logs e esteja atento a novas vulnerabilidades.

Nesta aula, você aprendeu a implementar autenticação com sessões, lembrar-me e logout seguro. Pratique os exercícios e adapte-os para seu projeto real, sempre priorizando a segurança.