Resumo : neste tutorial, você aprenderá como verificar o endereço de e-mail da nova conta com segurança usando um link de ativação.
Introdução à verificação de e-mail PHP para novas contas
Nos tutoriais anteriores, você aprendeu como criar um formulário de registro que permite aos usuários registrarem-se em contas. E você também aprendeu como criar um formulário de login que permitirá aos usuários usar o nome de usuário e a senha para fazer login.
Quando os usuários se registram em novas contas, eles inserem seus endereços de e-mail. No entanto, os usuários podem inserir qualquer endereço de e-mail porque o sistema não verifica o e-mail.
Para verificar os endereços de e-mail dos usuários, você pode enviar um e-mail de verificação para esses endereços de e-mail e solicitar que os usuários abram seus e-mails e cliquem em um link de ativação.
Para fazer isso, siga as seguintes etapas quando os usuários registrarem contas:
- Gere um código de ativação exclusivo e defina um prazo de validade, por exemplo, um dia.
- Salve o registro do usuário no banco de dados e marque o status do usuário como inativo. Além disso, salve o hash do código de ativação e do prazo de validade.
- Envie um email com o link de ativação para o endereço de email do usuário. O link de ativação conterá o endereço de e-mail e o código de ativação, por exemplo,
https://app.com/activate.php?email=email&activation_code=abcd
- Informe o usuário para ativar a conta por e-mail.
O hash do código de ativação garante que apenas o usuário que possui o endereço de e-mail possa ativar a conta, e mais ninguém, até mesmo o administrador, que possa acessar o banco de dados.
Se os usuários não tiverem a conta ativada, eles não poderão fazer login.
Quando os usuários clicam no link de ativação no e-mail, você precisa executar as seguintes etapas:
- Limpe e valide o e-mail e o código de ativação.
- Encontre o usuário inativo com o endereço de e-mail. Se não existir nenhum registro de usuário, redirecione para o formulário de registro.
- Se existir um registro de usuário e o código de ativação expirou, exclua o registro do usuário do banco de dados e redirecione para o formulário de registro.
- Caso contrário, combine o código de ativação com o hash do código de ativação armazenado no banco de dados. Se corresponderem, marque o registro do usuário como ativo e redirecione para a página de login.
Recrie a tabela de usuários
Primeiro, elimine a users
tabela do auth
banco de dados:
DROP TABLE users;
Linguagem de código: SQL (linguagem de consulta estruturada) ( sql )
Segundo, crie a users
tabela com as novas colunas active
, activation_code
, activation_at
, activation_expiry
:
CREATE TABLE users
(
id int auto_increment PRIMARY KEY,
username varchar(25) NOT NULL,
email varchar(255) NOT NULL,
password varchar(255) NOT NULL,
is_admin tinyint(1) NOT NULL DEFAULT 0,
active tinyint(1) DEFAULT 0,
activation_code varchar(255) NOT NULL,
activation_expiry datetime NOT NULL,
activated_at datetime DEFAULT NULL,
created_at timestamp NOT NULL DEFAULT current_timestamp(),
updated_at datetime DEFAULT current_timestamp() ON UPDATE current_timestamp()
);
Linguagem de código: SQL (linguagem de consulta estruturada) ( sql )
A seguir explica-se o significado das novas colunas.
O valor padrão da active
coluna é 0. Isso significa que os usuários que se cadastram em contas, mas não verificaram seus endereços de e-mail, ficarão inativos por padrão.
A activation_code
coluna armazenará o hash do código de ativação. Seu comprimento deve ser suficiente para armazenar a string retornada pela password_hash()
função.
É importante notar que o hash ficará truncado se a activation_code
coluna não tiver tamanho suficiente. Isso fará com que a password_verify()
função não corresponda ao código de ativação com o hash.
A activation_expiry
coluna armazena o prazo de validade para usar o código de ativação antes da expiração. O prazo de validade garante que o código de ativação não possa ser usado se o endereço de e-mail for comprometido após o prazo de validade.
A activated_at
coluna armazena a data e a hora em que os usuários ativam suas contas.
Estrutura do projeto
Vamos revisar a estrutura atual do projeto antes de adicionar as funções de verificação de e-mail:
├── config
| ├── app.php
| └── database.php
├── public
| ├── index.php
| ├── login.php
| ├── logout.php
| └── register.php
└── src
├── auth.php
├── bootstrap.php
├── inc
| ├── footer.php
| └── header.php
├── libs
| ├── connection.php
| ├── filter.php
| ├── flash.php
| ├── helpers.php
| ├── sanitization.php
| └── validation.php
├── login.php
└── register.php
Linguagem de código: PHP ( php )
Modifique as funções no arquivo auth.php
O seguinte adiciona o código de ativação e o parâmetro de expiração à register_user()
função. Por padrão, o prazo de validade é de um dia ( 1 * 24 * 60 * 60
).
function register_user(string $email, string $username, string $password, string $activation_code, int $expiry = 1 * 24 * 60 * 60, bool $is_admin = false): bool
{
$sql = 'INSERT INTO users(username, email, password, is_admin, activation_code, activation_expiry)
VALUES(:username, :email, :password, :is_admin, :activation_code,:activation_expiry)';
$statement = db()->prepare($sql);
$statement->bindValue(':username', $username);
$statement->bindValue(':email', $email);
$statement->bindValue(':password', password_hash($password, PASSWORD_BCRYPT));
$statement->bindValue(':is_admin', (int)$is_admin, PDO::PARAM_INT);
$statement->bindValue(':activation_code', password_hash($activation_code, PASSWORD_DEFAULT));
$statement->bindValue(':activation_expiry', date('Y-m-d H:i:s', time() + $expiry));
return $statement->execute();
}
Linguagem de código: PHP ( php )
A register_user()
função usa a password_hash()
função para fazer hash do código de ativação.
A find_user_by_username()
função inclui a active
coluna no resultado:
function find_user_by_username(string $username)
{
$sql = 'SELECT username, password, active, email
FROM users
WHERE username=:username';
$statement = db()->prepare($sql);
$statement->bindValue(':username', $username);
$statement->execute();
return $statement->fetch(PDO::FETCH_ASSOC);
}
Linguagem de código: PHP ( php )
O seguinte define uma nova função is_user_active()
que retorna true se um usuário estiver ativo:
function is_user_active($user)
{
return (int)$user['active'] === 1;
}
Linguagem de código: PHP ( php )
A login()
função deve permitir que apenas usuários ativos façam login:
function login(string $username, string $password): bool
{
$user = find_user_by_username($username);
if ($user && is_user_active($user) && password_verify($password, $user['password'])) {
// prevent session fixation attack
session_regenerate_id();
// set username in the session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return true;
}
return false;
}
Linguagem de código: PHP ( php )
Defina funções que tratam da verificação de e-mail
Adicionaremos as funções que tratam da verificação de e-mail ao auth.php
arquivo.
Primeiro, crie um novo arquivo app.php
na config
pasta e defina as seguintes constantes:
<?php
const APP_URL = 'http://localhost/auth';
const SENDER_EMAIL_ADDRESS = '[email protected]';
Linguagem de código: PHP ( php )
Usaremos essas constantes para enviar e-mails de ativação aos usuários. Para usar essas constantes, você precisa incluir o app.php
arquivo no bootstrap.php
arquivo:
<?php
session_start();
require_once __DIR__ . '/../config/app.php';
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/libs/helpers.php';
require_once __DIR__ . '/libs/flash.php';
require_once __DIR__ . '/libs/sanitization.php';
require_once __DIR__ . '/libs/validation.php';
require_once __DIR__ . '/libs/filter.php';
require_once __DIR__ . '/libs/connection.php';
require_once __DIR__ . '/auth.php';
Linguagem de código: PHP ( php )
Segundo, defina uma função que gere um código de ativação exclusivamente aleatório:
function generate_activation_code(): string
{
return bin2hex(random_bytes(16));
}
Linguagem de código: PHP ( php )
Terceiro, defina uma função que envie um e-mail de verificação com um link de ativação.
function send_activation_email(string $email, string $activation_code): void
{
// create the activation link
$activation_link = APP_URL . "/activate.php?email=$email&activation_code=$activation_code";
// set email subject & body
$subject = 'Please activate your account';
$message = <<<MESSAGE
Hi,
Please click the following link to activate your account:
$activation_link
MESSAGE;
// email header
$header = "From:" . SENDER_EMAIL_ADDRESS;
// send the email
mail($email, $subject, nl2br($message), $header);
}
Linguagem de código: PHP ( php )
Suponha que o URL do aplicativo seja http://localhost/auth
, o URL de ativação será semelhante a este:
http://localhost/auth/[email protected]&activation_code=e01e5c9a028d58d888ff2555b971c882
Linguagem de código: PHP ( php )
A send_activation_email()
função usa a função integrada mail()
para enviar e-mails.
Quarto, defina uma função que exclua um usuário por ID e status. Por padrão, ele exclui um usuário inativo por ID.
function delete_user_by_id(int $id, int $active = 0)
{
$sql = 'DELETE FROM users
WHERE id =:id and active=:active';
$statement = db()->prepare($sql);
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->bindValue(':active', $active, PDO::PARAM_INT);
return $statement->execute();
}
Linguagem de código: PHP ( php )
Quinto, defina uma função que encontre um usuário não verificado por meio de um e-mail e código de ativação. Se o código de ativação expirou, a função também exclui o registro do usuário chamando a delete_user_by_id()
função.
function find_unverified_user(string $activation_code, string $email)
{
$sql = 'SELECT id, activation_code, activation_expiry < now() as expired
FROM users
WHERE active = 0 AND email=:email';
$statement = db()->prepare($sql);
$statement->bindValue(':email', $email);
$statement->execute();
$user = $statement->fetch(PDO::FETCH_ASSOC);
if ($user) {
// already expired, delete the in active user with expired activation code
if ((int)$user['expired'] === 1) {
delete_user_by_id($user['id']);
return null;
}
// verify the password
if (password_verify($activation_code, $user['activation_code'])) {
return $user;
}
}
return null;
}
Linguagem de código: PHP ( php )
Sexto, defina uma nova activate_user()
função que ative um usuário por um id:
function activate_user(int $user_id): bool
{
$sql = 'UPDATE users
SET active = 1,
activated_at = CURRENT_TIMESTAMP
WHERE id=:id';
$statement = db()->prepare($sql);
$statement->bindValue(':id', $user_id, PDO::PARAM_INT);
return $statement->execute();
}
Linguagem de código: PHP ( php )
Modifique a página Register.php
A src/register.php
necessidade de incorporar a lógica para lidar com a lógica de verificação de e-mail.
<?php
if (is_user_logged_in()) {
redirect_to('index.php');
}
$errors = [];
$inputs = [];
if (is_post_request()) {
$fields = [
'username' => 'string | required | alphanumeric | between: 3, 25 | unique: users, username',
'email' => 'email | required | email | unique: users, email',
'password' => 'string | required | secure',
'password2' => 'string | required | same: password',
'agree' => 'string | required'
];
// custom messages
$messages = [
'password2' => [
'required' => 'Please enter the password again',
'same' => 'The password does not match'
],
'agree' => [
'required' => 'You need to agree to the term of services to register'
]
];
[$inputs, $errors] = filter($_POST, $fields, $messages);
if ($errors) {
redirect_with('register.php', [
'inputs' => escape_html($inputs),
'errors' => $errors
]);
}
$activation_code = generate_activation_code();
if (register_user($inputs['email'], $inputs['username'], $inputs['password'], $activation_code)) {
// send the activation email
send_activation_email($inputs['email'], $activation_code);
redirect_with_message(
'login.php',
'Please check your email to activate your account before signing in'
);
}
} else if (is_get_request()) {
[$errors, $inputs] = session_flash('errors', 'inputs');
}
Linguagem de código: PHP ( php )
Como funciona.
Primeiro, gere um código de ativação:
$activation_code = generate_activation_code();
Linguagem de código: PHP ( php )
Segundo, registre o usuário com o código de ativação:
register_user($inputs['email'], $inputs['username'], $inputs['password'], $activation_code)
Linguagem de código: PHP ( php )
Terceiro, envie um email para o endereço de email do usuário chamando a send_activation_email()
função:
send_activation_email($inputs['email'], $activation_code);
Linguagem de código: PHP ( php )
Por fim, redirecione o usuário para a página de login e mostre uma mensagem flash solicitando que o usuário ative a conta por e-mail:
redirect_with_message(
'login.php',
'Please check your email to activate your account before signing in'
);
Linguagem de código: PHP ( php )
Crie a página activate.php
Para permitir que os usuários ativem suas contas após o registro, você pode criar uma nova activate.php
página na public
pasta e usar a seguinte página:
<?php
require __DIR__ . '/../src/bootstrap.php';
if (is_get_request()) {
// sanitize the email & activation code
[$inputs, $errors] = filter($_GET, [
'email' => 'string | required | email',
'activation_code' => 'string | required'
]);
if (!$errors) {
$user = find_unverified_user($inputs['activation_code'], $inputs['email']);
// if user exists and activate the user successfully
if ($user && activate_user($user['id'])) {
redirect_with_message(
'login.php',
'You account has been activated successfully. Please login here.'
);
}
}
}
// redirect to the register page in other cases
redirect_with_message(
'register.php',
'The activation link is not valid, please register again.',
FLASH_ERROR
);
Linguagem de código: PHP ( php )
Como funciona o activate.php.
Primeiro, higienize e valide o e-mail e o código de ativação:
[$inputs, $errors] = filter($_GET, [
'email' => 'string | required | email',
'activation_code' => 'string | required'
]);
Linguagem de código: PHP ( php )
Em segundo lugar, encontre o usuário não verificado com base no e-mail e no código de verificação, se não houver erros de validação. O find_unverified_user()
também excluirá o usuário não verificado se o tempo de expiração expirar.
$user = find_unverified_user($inputs['activation_code'], $inputs['email']);
Linguagem de código: PHP ( php )
Terceiro, ative o usuário e redirecione para a página login.php:
if ($user && activate_user($user['id'])) {
redirect_with_message(
'login.php',
'You account has been activated successfully. Please login here.'
);
}
Linguagem de código: PHP ( php )
Por fim, redirecione para se registration.php
houver um erro:
redirect_with_message(
'register.php',
'The activation link is not valid, please register again.',
FLASH_ERROR
);
Linguagem de código: PHP ( php )
Neste tutorial, você aprendeu como implementar a verificação de email para contas de usuário em PHP.