Como implementar autenticação de dois fatores em PHP

Implementando um sistema de login com suporte a autenticação de dois fatores (2FA) em PHP

07/02/2024

Como implementar autenticação de dois fatores em PHP

Como implementar autenticação de dois fatores em PHP

Introdução

Autenticação de dois fatores (2FA) é uma técnica de segurança que adiciona uma camada extra de proteção ao processo de login, além da tradicional senha. Para obter acesso ao sistema, o usuário precisa informar um código gerado por um aplicativo de autenticação, como Google Authenticator ou Authy.

Este tutorial ensina como implementar autenticação de dois fatores (2FA) em aplicações PHP.

Integração da Biblioteca do Google Authenticator

Para implementar a autenticação de dois fatores (2FA) na sua aplicação PHP, é necessário utilizar a biblioteca do Google Authenticator.

Primeiro, faça o download da biblioteca do Google Authenticator. Em seguida, extraia o arquivo GoogleAuthenticator.php para o diretório do seu projeto.

Esta etapa é essencial para habilitar a funcionalidade de 2FA na sua aplicação, permitindo a criação de URLs para QR Codes e a validação de códigos de autenticação.

Preparando o Banco de Dados MySQL

Precisamos preparar o banco de dados, criando uma tabela users para armazenar os dados dos usuários:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(255) NOT NULL,
    secret VARCHAR(255),
    2fa_enabled TINYINT(1) DEFAULT 0
);

Após criar a tabela, insira um usuário de teste usando o seguinte comando SQL:

INSERT INTO users (username, password, 2fa_enabled) VALUES ('teste', SHA1('123testando'), 0);

A função SHA1 é usada para que a senha seja gravada de forma criptografada no banco de dados, aumentando a segurança.

Configuração do Banco de Dados (db.php)

Para conectar sua aplicação PHP ao banco de dados MySQL, criaremos um arquivo chamado db.php com as configurações de conexão com o banco de dados.

<?php
$host = 'localhost';
$db   = 'seu_banco';
$user = 'seu_usuario';
$pass = 'sua_senha';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
?>

Este arquivo será incluído nos scripts PHP que necessitam interagir com o banco. Certifique-se de substituir seu_banco, seu_usuario, e sua_senha pelas informações reais de acesso ao seu banco de dados MySQL.

Verificação do Login e do Estágio da Autenticação (check_login.php)

Este script garante que apenas usuários autenticados acessem páginas restritas. Ele verifica se o usuário está logado e se passou pela autenticação de dois fatores, quando necessário.

<?php
// Inicia a sessão caso ainda não tenha sido iniciada
if (session_status() === PHP_SESSION_NONE) session_start();

// Se o usuário não estiver logado, redireciona para login.php
if (!isset($_SESSION['user_id']) || $_SESSION['user_id'] <= 0) {
    header('Location: login.php');
    exit();
}

// Se o estágio de autenticação não estiver completo, redireciona para verify_2fa.php
if (!isset($_SESSION['auth_stage']) || $_SESSION['auth_stage'] != 1) {
    header('Location: verify_2fa.php');
    exit();
}
?>

Esse script deve ser incluído no topo de cada página que requer autenticação do usuário. Ele utiliza a variável de sessão user_id para verificar se o usuário está logado e a variável auth_stage para conferir se a autenticação de dois fatores foi completada. A inclusão condicional de session_start() previne avisos indesejados se a sessão já estiver ativa.

Login e Processamento do Login (login.php)

Este arquivo atua como o formulário de login e também processa a tentativa de login. Após a autenticação bem-sucedida, o usuário é redirecionado com base no estado de sua autenticação de dois fatores.

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    require_once 'db.php'; // Conexão com o banco de dados

    $username = $_POST['username'];
    $stmt = $pdo->prepare("SELECT id, password, 2fa_enabled FROM users WHERE username = :username");
    $stmt->execute([':username' => $username]);
    $user = $stmt->fetch();

    if ($user && sha1($_POST['password']) === $user['password']) {
        // Inicia a sessão caso
        if (session_status() === PHP_SESSION_NONE) session_start();

        // Define user_id como o id do usuário
        $_SESSION['user_id'] = $user['id'];

        // Define auth_stage: 0 para necessidade de 2FA, 1 para autenticação completa
        $_SESSION['auth_stage'] = $user['2fa_enabled'] ? 0 : 1;

        // Redireciona o usuário
        if ($_SESSION['auth_stage'] === 0) {
            header('Location: verify_2fa.php'); // Necessita de verificação 2FA
            exit();
        } else {
            header('Location: menu.php'); // Autenticação completa
            exit();
        }
    } else {
        $error = "Usuário ou senha inválidos.";
    }
}
?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <?php if (isset($error)): ?>
        <p style="color: red;"><?php echo htmlspecialchars($error); ?></p>
    <?php endif; ?>
    <form action="login.php" method="post">
        Usuário: <input type="text" name="username"><br>
        Senha: <input type="password" name="password"><br>
        <input type="submit" value="Login">
    </form>
</body>
</html>

Este script busca o usuário pelo nome de usuário utilizando parâmetros nomeados para a consulta SQL. Caso as credenciais estejam corretas, verifica se a autenticação de dois fatores está ativada e redireciona o usuário adequadamente.

Verificação do 2FA (verify_2fa.php)

Este script processa a validação do código de autenticação de dois fatores fornecido pelo usuário após o login inicial.

<?php
// Inicia a sessão
if (session_status() === PHP_SESSION_NONE) session_start();

// Se o usuário não está logado, retorna o usuário para a página de login
if (!isset($_SESSION['user_id']) || $_SESSION['user_id'] <= 0) {
    header('Location: login.php');
    exit;
}

// Inclui a biblioteca do Google Authenticator apenas se necessário
require_once 'GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Conecta com o banco de dados
    require_once 'db.php';

    $userQuery = $pdo->prepare("SELECT secret FROM users WHERE id = :id");
    $userQuery->execute([':id' => $_SESSION['user_id']]);
    $userInfo = $userQuery->fetch();

    if ($userInfo && $ga->verifyCode($userInfo['secret'], $_POST['code'], 2)) {
        // Atualiza o estágio de autenticação na sessão
        $_SESSION['auth_stage'] = 1;
        header('Location: menu.php');
        exit;
    } else {
        $error = "Código de verificação inválido.";
    }
}
?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Verificação 2FA</title>
</head>
<body>
    <form action="verify_2fa.php" method="post">
        <p>Digite o código de verificação do aplicativo:</p>
        <input type="text" name="code" required>
        <input type="submit" value="Verificar Código">
    </form>
    <?php if (isset($error)): ?>
        <p style="color: red;"><?php echo htmlspecialchars($error); ?></p>
    <?php endif; ?>
</body>
</html>

Este script verifica o código 2FA fornecido e atualiza o estado da autenticação do usuário, permitindo acesso apenas após a verificação bem-sucedida.

Página Principal do Sistema (menu.php)

Esta página exibe informações personalizadas para o usuário logado, incluindo opções para configurar o 2FA ou sair do sistema.

<?php

// Verifica se o usuário está logado
require_once 'check_login.php';

// Conecta com o banco de dados
require_once 'db.php';

$stmt = $pdo->prepare("SELECT username FROM users WHERE id = :id");
$stmt->execute([':id' => $_SESSION['user_id']]);
$user = $stmt->fetch();

if (!$user) {
    header('Location: login.php'); // Redireciona se o usuário não for encontrado
    exit;
}

$username = $user['username'];

?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Menu Principal</title>
</head>
<body>
    <h1>Bem-vindo, <?php echo htmlspecialchars($username); ?></h1>
    <p><a href="setup_2fa.php">Configurar Autenticação de Dois Fatores (2FA)</a></p>
    <p><a href="logout.php">Sair</a></p>
</body>
</html>

Nesta página, o usuário verá todas as opções de acesso ao sistema. Neste exemplo temos apenas a possibilidade de configurar a autenticação de dois fatores e um link para sair do sistema e retornar à página de login.

Configuração da Autenticação de Dois Fatores (setup_2fa.php)

Este script guia o usuário na configuração do 2FA, alertando sobre a substituição da configuração anterior se já estiver ativa. A nova configuração só é salva após a verificação bem-sucedida do código gerado pelo aplicativo.

<?php
// Verifica se o usuário está logado
require_once 'check_login.php';

// Inclui a biblioteca do Google Authenticator corretamente
require_once 'GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();

// Conexão com o banco de dados 
require_once 'db.php'; 

// Busca configuração 2FA existente
$stmt = $pdo->prepare("SELECT secret, 2fa_enabled FROM users WHERE id = :id");
$stmt->execute([':id' => $_SESSION['user_id']]);
$userInfo = $stmt->fetch();

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['code']) && isset($_SESSION['temp_secret'])) {
    $code = $_POST['code'];
    $temp_secret = $_SESSION['temp_secret'];

    if ($ga->verifyCode($temp_secret, $code, 2)) {
        // Atualiza o banco de dados com o novo segredo após a verificação bem-sucedida
        $stmt = $pdo->prepare("UPDATE users SET secret = :secret, 2fa_enabled = 1 WHERE id = :id");
        $stmt->execute([':secret' => $temp_secret, ':id' => $_SESSION['user_id']]);
        unset($_SESSION['temp_secret']); // Limpa o segredo temporário da sessão
        header('Location: menu.php');
        exit;
    } else {
        $error = 'Código de verificação inválido. Por favor, tente novamente.';
    }
} else {
    // Gera e armazena um novo código secreto temporariamente
    $temp_secret = $ga->createSecret();
    $_SESSION['temp_secret'] = $temp_secret;
    $qrCodeUrl = $ga->getQRCodeGoogleUrl($_SERVER['HTTP_HOST'], $temp_secret);
}

// Alerta se o 2FA já estiver configurado
$warning = $userInfo['2fa_enabled'] ? "Atenção: Configurar o 2FA novamente desativará o dispositivo anteriormente sincronizado." : "";
?><!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Configurar 2FA</title>
</head>
<body>
    <h1>Configuração da Autenticação de Dois Fatores</h1>
    <?php if (!empty($warning)): ?>
        <p style="color: red;"><?= htmlspecialchars($warning); ?></p>
    <?php endif; ?>
    <?php if (!empty($error)): ?>
        <p style="color: red;"><?= htmlspecialchars($error); ?></p>
    <?php endif; ?>
    <p>Escaneie o QR Code abaixo e insira o código gerado pelo seu aplicativo.</p>
    <img src="<?= htmlspecialchars($qrCodeUrl); ?>" alt="QR Code" />
    <form action="" method="post">
        Código: <input type="text" name="code" required>
        <input type="submit" value="Verificar e Ativar 2FA">
    </form>
    <p><a href="menu.php">Voltar</a></p>
</body>
</html>

Logout (logout.php)

Este script é responsável por encerrar a sessão do usuário, limpando as variáveis de sessão existentes e destruindo a sessão ativa. Após essas ações, o usuário é redirecionado para a página de login. Desta forma, garante-se que nenhuma informação de sessão persista após o logout, mantendo a segurança da aplicação.

<?php
// Inicia a sessão
session_start(); 

// Limpa e destrói a sessão
$_SESSION = array();
session_destroy();

// Redireciona para a página de login
header('Location: login.php');
exit;
?>

Observações Importantes para Implementação do 2FA

Ao implementar a autenticação de dois fatores (2FA) em sua aplicação PHP, é crucial seguir boas práticas de segurança e personalizar o código para atender às necessidades específicas da sua aplicação. Abaixo estão algumas observações importantes a serem consideradas:

  • Proteção de Páginas: Inclua o script check_login.php em todas as páginas PHP do sistema que só devem ser acessadas por usuários logados. A omissão desta inclusão pode resultar em uma falha de segurança, permitindo acesso não autorizado a áreas restritas da aplicação.
  • Consultas Seguras ao Banco de Dados: Sempre utilize consultas parametrizadas ao fazer interações com o banco de dados. As consultas parametrizadas protegem sua aplicação contra vulnerabilidades de SQL Injection, uma das formas mais comuns de ataques cibernéticos.
  • Personalização e Validações: O exemplo fornecido neste tutorial apresenta um código mínimo necessário para implementar o 2FA. É importante personalizar esse código para se adequar ao fluxo de trabalho e às exigências de segurança da sua aplicação. Além disso, adicione validações adicionais conforme necessário para reforçar a segurança.
  • Design e Usabilidade: Embora o foco deste tutorial seja a funcionalidade de 2FA, não se esqueça de formatar o design das páginas de forma apropriada. Uma boa experiência do usuário é fundamental para garantir que os processos de login e configuração do 2FA sejam intuitivos e acessíveis.

Conclusão

Implementar a autenticação de dois fatores é um passo significativo para aumentar a segurança da sua aplicação. No entanto, a segurança é um processo contínuo que requer vigilância e atualizações regulares. Mantenha-se informado sobre as melhores práticas de segurança e esteja preparado para adaptar e atualizar sua implementação conforme necessário.

Domínios hospedados
Clientes satisfeitos