Já escrevi uma postagem no blog sobre a criação de um sistema completo de registro e login de usuários usando PHP e MySQL, mas não incluí a verificação de e-mail.
Este tutorial é um pouco como uma atualização do tutorial anterior. Além de poder se registrar, fazer login e sair da conta, o usuário também receberá um e-mail de verificação para o endereço de e-mail com um link para clicar e verificar o endereço de e-mail.
Na maioria dos aplicativos que você vai construir, é importante adicionar um recurso de verificação de e-mail por vários motivos:você pode querer enviar um e-mail mais tarde para o usuário e ter certeza de que ele o verá; ou o usuário pode esquecer sua senha e precisar redefini-la e, para isso, precisaremos enviar um link de redefinição de senha por e-mail; há muitas outras razões, mas você entendeu.
Neste sistema que estamos construindo hoje, os usuários que não verificaram seus e-mails não poderão realizar determinadas ações (vou usar apenas uma demonstração simples, como visualizar um botão. Somente usuários verificados poderão ver esse botão).
Para começar, crie um novo projeto PHP chamado Verify-user e nesta pasta, crie dois arquivos:signup.php e login.php.
signup.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="main.css">
<title>User verification system PHP</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4 form-wrapper auth">
<h3 class="text-center form-title">Register</h3>
<form action="signup.php" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" class="form-control form-control-lg" value="<?php echo $username; ?>">
</div>
<div class="form-group">
<label>Email</label>
<input type="text" name="email" class="form-control form-control-lg" value="<?php echo $email; ?>">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control form-control-lg">
</div>
<div class="form-group">
<label>Password Confirm</label>
<input type="password" name="passwordConf" class="form-control form-control-lg">
</div>
<div class="form-group">
<button type="submit" name="signup-btn" class="btn btn-lg btn-block">Sign Up</button>
</div>
</form>
<p>Already have an account? <a href="login.php">Login</a></p>
</div>
</div>
</div>
</body>
</html>
É apenas um arquivo HTML/CSS simples. A única coisa que vale a pena notar é que estamos usando o framework CSS Bootstrap 4 para estilizar nossa página. Você pode usar qualquer outra estrutura de estilo de sua escolha ou escrever seu próprio CSS, se desejar.
Imediatamente após o CSS do Bootstrap, estamos incluindo um arquivo main.css para um estilo personalizado. Vamos criar esse arquivo agora. Na pasta raiz do aplicativo, crie um arquivo chamado main.css.
main.css:
@import url('https://fonts.googleapis.com/css?family=Lora');
li { list-style-type: none; }
.form-wrapper {
margin: 50px auto 50px;
font-family: 'Lora', serif;
font-size: 1.09em;
}
.form-wrapper.login { margin-top: 120px; }
.form-wrapper p { font-size: .8em; text-align: center; }
.form-control:focus { box-shadow: none; }
.form-wrapper {
border: 1px solid #80CED7;
border-radius: 5px;
padding: 25px 15px 0px 15px;
}
.form-wrapper.auth .form-title { color: #007EA7; }
.home-wrapper button,
.form-wrapper.auth button {
background: #007EA7;
color: white;
}
.home-wrapper {
margin-top: 150px;
border-radius: 5px;
padding: 10px;
border: 1px solid #80CED7;
}
Na primeira linha deste arquivo estamos importando e usando algumas fontes do Google para deixar nossas fontes mais bonitas.
Agora vá para o arquivo login.php e faça algo semelhante.
login.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="main.css">
<title>User verification system PHP - Login</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4 form-wrapper auth login">
<h3 class="text-center form-title">Login</h3>
<form action="login.php" method="post">
<div class="form-group">
<label>Username or Email</label>
<input type="text" name="username" class="form-control form-control-lg" value="<?php echo $username; ?>">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control form-control-lg">
</div>
<div class="form-group">
<button type="submit" name="login-btn" class="btn btn-lg btn-block">Login</button>
</div>
</form>
<p>Don't yet have an account? <a href="signup.php">Sign up</a></p>
</div>
</div>
</div>
</body>
</html>
No seu navegador, vá para http://localhost/cwa/verify-user/signup.php você verá um belo formulário de inscrição (o mesmo para login). Ignore os erros nos campos de entrada, corrigiremos isso em breve.
Por enquanto, vamos configurar o banco de dados. Crie um banco de dados chamado Verify-user e, nesse banco de dados, crie uma tabela de usuários com os seguintes atributos:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`email` varchar(100) NOT NULL,
`verified` tinyint(1) NOT NULL DEFAULT '0',
`token` varchar(255) DEFAULT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
)
Nada incomum, exceto, talvez, o token e os campos verificados, que explicarei em breve.
Agora começamos com a lógica de inscrição real. Eu geralmente gosto de me referir à parte lógica do meu aplicativo como os controladores e é isso que farei aqui. Na pasta raiz do projeto, crie uma pasta chamada controllers e dentro de controllers, crie um arquivo chamado authController.php.
controllers/authController.php:
<?php
session_start();
$username = "";
$email = "";
$errors = [];
$conn = new mysqli('localhost', 'root', '', 'verify-user');
// SIGN UP USER
if (isset($_POST['signup-btn'])) {
if (empty($_POST['username'])) {
$errors['username'] = 'Username required';
}
if (empty($_POST['email'])) {
$errors['email'] = 'Email required';
}
if (empty($_POST['password'])) {
$errors['password'] = 'Password required';
}
if (isset($_POST['password']) && $_POST['password'] !== $_POST['passwordConf']) {
$errors['passwordConf'] = 'The two passwords do not match';
}
$username = $_POST['username'];
$email = $_POST['email'];
$token = bin2hex(random_bytes(50)); // generate unique token
$password = password_hash($_POST['password'], PASSWORD_DEFAULT); //encrypt password
// Check if email already exists
$sql = "SELECT * FROM users WHERE email='$email' LIMIT 1";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
$errors['email'] = "Email already exists";
}
if (count($errors) === 0) {
$query = "INSERT INTO users SET username=?, email=?, token=?, password=?";
$stmt = $conn->prepare($query);
$stmt->bind_param('ssss', $username, $email, $token, $password);
$result = $stmt->execute();
if ($result) {
$user_id = $stmt->insert_id;
$stmt->close();
// TO DO: send verification email to user
// sendVerificationEmail($email, $token);
$_SESSION['id'] = $user_id;
$_SESSION['username'] = $username;
$_SESSION['email'] = $email;
$_SESSION['verified'] = false;
$_SESSION['message'] = 'You are logged in!';
$_SESSION['type'] = 'alert-success';
header('location: index.php');
} else {
$_SESSION['error_msg'] = "Database error: Could not register user";
}
}
}
// LOGIN
if (isset($_POST['login-btn'])) {
if (empty($_POST['username'])) {
$errors['username'] = 'Username or email required';
}
if (empty($_POST['password'])) {
$errors['password'] = 'Password required';
}
$username = $_POST['username'];
$password = $_POST['password'];
if (count($errors) === 0) {
$query = "SELECT * FROM users WHERE username=? OR email=? LIMIT 1";
$stmt = $conn->prepare($query);
$stmt->bind_param('ss', $username, $password);
if ($stmt->execute()) {
$result = $stmt->get_result();
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) { // if password matches
$stmt->close();
$_SESSION['id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['verified'] = $user['verified'];
$_SESSION['message'] = 'You are logged in!';
$_SESSION['type'] = 'alert-success';
header('location: index.php');
exit(0);
} else { // if password does not match
$errors['login_fail'] = "Wrong username / password";
}
} else {
$_SESSION['message'] = "Database error. Login failed!";
$_SESSION['type'] = "alert-danger";
}
}
}
Se você seguiu meus tutoriais anteriores, nada neste arquivo deve ser novo para você. Mas para os novatos, vou dar algumas explicações.
A primeira coisa é que estamos iniciando a sessão usando session_start(), pois precisaremos armazenar as informações do usuário logado na sessão. Após iniciar a sessão, estamos inicializando as variáveis $username e $email que estamos usando em nossos formulários, e também o array $errors que conterá nossos erros de validação de formulário.
Em seguida, nos conectamos ao banco de dados. As próximas duas instruções if a seguir são, respectivamente, o código que é executado quando o usuário clica nos botões de inscrição ou login. No caso de cadastro, verificamos se todos os campos obrigatórios foram preenchidos corretamente e só então procedemos ao salvamento do usuário no banco de dados. Também estamos gerando um token (uma string única e aleatória) e salvando-o com user como atributo. Isso será usado para verificar o e-mail do usuário. Mais sobre isso mais tarde.
Já que nosso arquivo authController.php é responsável pela inscrição e login, temos que incluí-lo no topo das páginas signup.php e login.php porque é para onde os dados do formulário estão sendo enviados. Igual a:
signup.php e login.php (no topo):
<?php include 'controllers/authController.php' ?>
Se houver alguma mensagem de erro no array $errors, precisamos exibi-la no formulário. Para fazer isso, adicione esta instrução if dentro do seu formulário diretamente abaixo do título do formulário para as páginas de inscrição e login.
<!-- form title -->
<h3 class="text-center form-title">Register</h3> <!-- or Login -->
<?php if (count($errors) > 0): ?>
<div class="alert alert-danger">
<?php foreach ($errors as $error): ?>
<li>
<?php echo $error; ?>
</li>
<?php endforeach;?>
</div>
<?php endif;?>
Se não houver erros, nosso script continuará salvando o usuário no banco de dados. Depois de salvar o usuário no banco de dados, nós o logamos imediatamente. No nosso caso, fazer login de um usuário significa armazenar seus dados na sessão e foi isso que acabamos de fazer.
Neste ponto, você já pode se cadastrar e até logar um usuário. Mas após o login, você será redirecionado para a página index.php que não existe. Vamos criar em breve.
Em authController.php, estamos armazenando mensagem e variáveis de tipo na sessão a serem exibidas assim que o usuário fizer login. message é o texto real da mensagem enquanto o tipo é a classe de estilo Bootstrap que formatará a mensagem com cores dependendo do valor do tipo.
Esta mensagem é exibida após o usuário efetuar login e é exibida no arquivo index.php. Vamos criar esse arquivo agora na pasta raiz do nosso projeto.
index.php:
<?php include 'controllers/authController.php'?>
<?php
// redirect user to login page if they're not logged in
if (empty($_SESSION['id'])) {
header('location: login.php');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="main.css">
<title>User verification system PHP</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4 home-wrapper">
<!-- Display messages -->
<?php if (isset($_SESSION['message'])): ?>
<div class="alert <?php echo $_SESSION['type'] ?>">
<?php
echo $_SESSION['message'];
unset($_SESSION['message']);
unset($_SESSION['type']);
?>
</div>
<?php endif;?>
<h4>Welcome, <?php echo $_SESSION['username']; ?></h4>
<a href="logout.php" style="color: red">Logout</a>
<?php if (!$_SESSION['verified']): ?>
<div class="alert alert-warning alert-dismissible fade show" role="alert">
You need to verify your email address!
Sign into your email account and click
on the verification link we just emailed you
at
<strong><?php echo $_SESSION['email']; ?></strong>
</div>
<?php else: ?>
<button class="btn btn-lg btn-primary btn-block">I'm verified!!!</button>
<?php endif;?>
</div>
</div>
</div>
</body>
</html>
Esta página destina-se a ser acessível apenas para usuários logados. É por isso que, na parte superior do arquivo, estamos redirecionando o usuário para a página de login se ele não estiver logado. Em algum lugar no centro da página, estamos exibindo a mensagem. Depois de exibir a mensagem, desdefinimos a mensagem e digitamos as variáveis porque não queremos que ela permaneça na página mesmo depois que o usuário atualizar a página.
Por fim, no centro da página, verificamos se o usuário conectado confirmou o endereço de e-mail ou não. Lembre-se de que adicionamos a variável verificada na sessão quando fizemos login do usuário. Se o usuário foi verificado, exibimos a mensagem "Estou verificado!!!" botão para eles verem. Se eles não forem verificados, informamos a eles sobre o link de verificação que enviamos para o endereço de e-mail deles e pedimos que cliquem nesse link para verificar o e-mail.
Verificar e-mail
No arquivo authController.php, usamos um comentário para indicar para onde enviaríamos o e-mail de verificação ao usuário chamando o método sendVerificationEmail(). Vá para o arquivo authController.php e descomente a chamada da função assim:
// TO DO: send verification email to user
sendVerificationEmail($email, $token);
Vamos definir esta função em outro arquivo e incluir esse arquivo dentro de authController.php. Dentro da pasta controllers, crie um arquivo chamado sendEmails.php.
Antes de adicionarmos qualquer código neste arquivo, deixe-me falar um pouco sobre o PHP SwiftMailer, a popular biblioteca para envio de e-mails em PHP que vamos usar neste projeto para enviar e-mails do localhost.
SwiftMailer é uma biblioteca popular rica em recursos para enviar e-mails em aplicativos PHP.
Para usar o Swiftmailer, primeiro você precisa instalar o Composer. Depois de instalar o composer, abra seu terminal ou linha de comando e navegue até a pasta raiz do projeto e execute o seguinte comando para adicionar a biblioteca Swift Mailer com todos os seus arquivos ao nosso projeto:
composer require "swiftmailer/swiftmailer:^6.0"
Isso cria uma pasta de fornecedor na raiz do nosso aplicativo contendo todo o código (classes) necessário para enviar um e-mail e também cria um arquivo composer.json na raiz do aplicativo com esta aparência:
{
"require": {
"swiftmailer/swiftmailer": "^6.0"
}
}
Agora abra o arquivo sendEmails.php que criamos anteriormente e vamos escrever a função sendVerificationEmail():
<?php
require_once './vendor/autoload.php';
// Create the Transport
$transport = (new Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl'))
->setUsername(SENDER_EMAIL)
->setPassword(SENDER_PASSWORD);
// Create the Mailer using your created Transport
$mailer = new Swift_Mailer($transport);
function sendVerificationEmail($userEmail, $token)
{
global $mailer;
$body = '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test mail</title>
<style>
.wrapper {
padding: 20px;
color: #444;
font-size: 1.3em;
}
a {
background: #592f80;
text-decoration: none;
padding: 8px 15px;
border-radius: 5px;
color: #fff;
}
</style>
</head>
<body>
<div class="wrapper">
<p>Thank you for signing up on our site. Please click on the link below to verify your account:.</p>
<a href="http://localhost/cwa/verify-user/verify_email.php?token=' . $token . '">Verify Email!</a>
</div>
</body>
</html>';
// Create a message
$message = (new Swift_Message('Verify your email'))
->setFrom(SENDER_EMAIL)
->setTo($userEmail)
->setBody($body, 'text/html');
// Send the message
$result = $mailer->send($message);
if ($result > 0) {
return true;
} else {
return false;
}
}
A primeira instrução requer o arquivo autoload.php neste arquivo. Este arquivo autoload.php incluirá automaticamente todas as classes da biblioteca Swift Mailer na pasta do fornecedor que usamos neste arquivo.
Estamos usando o Gmail neste exemplo. Assim, você pode substituir SENDER_EMAIL e SENDER_PASSWORD pelo endereço e senha do Gmail que deseja usar como endereço de e-mail do remetente. (O endereço de e-mail do destinatário é aquele enviado através do formulário).
Normalmente, para enviar um e-mail para alguém, você precisa fazer login na sua conta do Gmail antes de compor o e-mail e enviá-lo. É o mesmo tipo de coisa que a biblioteca Swift Mailer faz. Assim, quando o destinatário ($userEmail) receber o e-mail, será o seu endereço do Gmail (SENDER_EMAIL) que ele verá como o e-mail de envio.
Agora estamos chamando a função sendVerificationEmail() em nosso arquivo authController.php, mas definimos a função dentro do arquivo sendEmails.php. Vamos incluir o arquivo sendEmails.php dentro do nosso authController.php para disponibilizar esta função no arquivo. No topo de authController.php, logo antes de session_start(), adicione a seguinte linha:
require_once 'sendEmails.php';
Isso é tudo que precisamos, senhores (e senhoras) para enviar um e-mail ao nosso usuário com um link de verificação de e-mail. Mas estamos trabalhando no localhost e o Gmail bloqueará qualquer tentativa de login do Swift Mailer em execução no localhost.
Enviando e-mail do localhost
Se você quiser que isso seja executado no localhost, precisará configurar sua conta do Gmail para aceitar o login de aplicativos menos seguros. Claro, isso pode representar alguma vulnerabilidade em sua conta do Gmail, mas você pode fazer isso apenas pelo curto período de tempo que você precisa para testar este aplicativo no localhost. Após o teste, você pode desfazer a configuração em sua conta do Gmail. Depois que seu aplicativo estiver hospedado na Internet, você trabalhará. Estamos fazendo isso apenas porque estamos no localhost.
Você pode ser ainda mais cauteloso e criar outra conta do Gmail apenas para fins como esses.
Portanto, faça login no Gmail no navegador, acesse https://myaccount.google.com/security#connectedapps e altere o valor "Permitir aplicativos menos seguros" para ATIVADO.
Você pode desativar isso quando terminar de testar o projeto no localhost.
Com isso, você poderá enviar um e-mail do localhost com link de verificação assim que o usuário se inscrever. Agora olhe novamente o método sendVerificationEmail() e você notará que no corpo do email que estamos enviando para o usuário, o token que geramos para aquele usuário em particular (o token é único) foi definido como parâmetro no link para que, quando o usuário clicar no link do e-mail, ele seja direcionado ao nosso aplicativo em uma página chamada verify_email.php com esse token no URL. Assim:
<a href="http://localhost/cwa/verify-user/verify_email.php?token=0a150966418fa3a694bcb3ab8fcacd2063a096accc0ee33c3e8c863538ee825c0b52f2e1535d0e1377558c378ba5fc3106eb">Verify Email!</a>
Então podemos pegar esse token no nosso Verify_email.php assim (calma, vamos criar esse Verify_email.php em breve):
$token = $_GET['token'];
Agora podemos usar esse token para buscar o usuário que possui esse token específico (lembre-se que o token é único) e se obtivermos esse usuário, atualizaremos seu registro alterando o atributo verificado para true no banco de dados. Então, podemos dizer com orgulho que verificamos o endereço de e-mail desse usuário.
Vamos criar este arquivo Verify_email.php na pasta raiz do nosso projeto:
verifique_email.php:
<?php
session_start();
$conn = new mysqli('localhost', 'root', '', 'verify-user');
if (isset($_GET['token'])) {
$token = $_GET['token'];
$sql = "SELECT * FROM users WHERE token='$token' LIMIT 1";
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
$user = mysqli_fetch_assoc($result);
$query = "UPDATE users SET verified=1 WHERE token='$token'";
if (mysqli_query($conn, $query)) {
$_SESSION['id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['verified'] = true;
$_SESSION['message'] = "Your email address has been verified successfully";
$_SESSION['type'] = 'alert-success';
header('location: index.php');
exit(0);
}
} else {
echo "User not found!";
}
} else {
echo "No token provided!";
}
Observe que definir o valor do valor verifed como 1 é o mesmo que defini-lo como true, pois no banco de dados MySQL o tipo Boolean é interpretado como tinyint.
Agora, quando o usuário clica no link em seu e-mail e o leva para esta página, ele atualiza o status verificado desse usuário para verdadeiro, faz o login e o redireciona para a página index.php. Na página de índice, depois de verificar o usuário, você notará que a mensagem de aviso aconselhando o usuário a verificar seu endereço de e-mail desapareceu e em seu lugar temos o "Estou verificado!!!" botão que é visível apenas para usuários verificados.
Uma última coisa, na página index.php após o login do usuário, há um link de logout que aponta para um arquivo logout.php que deve fazer o logout do usuário. Vamos criar esse arquivo na raiz da nossa aplicação:
logout.php:
<?php
session_destroy();
unset($_SESSION['id']);
unset($_SESSION['username']);
unset($_SESSION['email']);
unset($_SESSION['verify']);
header("location: login.php");
Portanto, além de poder se inscrever, verificar e-mail, fazer login, o usuário agora também pode sair.
Conclusão
Então é isso com registro de usuário e verificação de e-mail. Se você tiver algum comentário, pergunta ou palavras de incentivo, por favor, deixe-os nos comentários abaixo. E por favor, lembre-se de compartilhar este post ou recomendar este site para seus amigos se você achou útil. Isso me incentiva muito!
Tenha um ótimo dia!