Alura > Cursos de Programação > Cursos de PHP > Conteúdos de PHP > Primeiras aulas do curso PHP na Web: lidando com segurança e API

PHP na Web: lidando com segurança e API

Segurança com autenticação - Apresentação

Boas-vindas à Alura! Me chamo Vinicius Dias, e serei o seu instrutor ao longo deste curso de PHP na Web: lidando com segurança e API.

Vinicius Dias é uma pessoa de pele clara e olhos escuros, com cabelos pretos e curtos. Usa bigode e cavanhaque e veste uma camiseta azul, com o escrito "PHP Community summit". Há um microfone de lapela na gola de sua camiseta. Ele está sentado em uma cadeira preta e, ao fundo, há uma parede lisa com iluminação azul gradiente.

Neste curso, daremos continuidade ao sistema que desenvolvemos no curso anterior de introdução ao PHP na Web. Para fazer este curso, é esperado que você já tenha concluído o anterior e que tenha todos os pré-requisitos.

No sistema que daremos continuidade, iniciaremos falando sobre senhas, como armazená-las de forma segura e como autenticar os usuários.

Na tela, o instrutor está exibindo uma tela de login com os campos de e-mail e senha, e abaixo um botão azul "Entrar". O endereço desta página é o http://localhost:8080/login. No canto superior esquerdo, temos um ícone de filmadora com o sinal de play ("▶") dentro, do lado direito temos o texto "AluraPlay". No canto superior direito, temos o ícone de uma filmadora com o sinal de mais ("+"), e ao lado direito um botão azul "Sair".

Ao tentarmos acessar o sistema com um e-mail errado, não conseguimos logar. Para testar, no campo "E-mail" digitaremos "teste@email.com", no campo "Senha" inserimos a senha. Por fim, clicamos no botão azul "Entrar", no canto inferior central.

E-mail: teste@email.com
Senha: *****

Agora, se preenchermos com os dados corretos conforme o que foi cadastrado, sim.

E-mail: email@example.com
Senha: *****

Em seguida, clicamos no botão "Entrar". Conseguimos logar no sistema, ou seja, além da autenticação, temos a autorização. Não permitimos o acesso nesta página inicial do sistema se o usuário não estiver logado.

Ao logarmos no sistema, temos três vídeos localizados horizontalmente na tela, respectivamente:

Todos com os botões "Editar" e "Excluir", na parte inferior.

Vamos aprender bastante sobre como armazenar as senhas de forma segura, e vimos como usar sessões para conseguirmos identificar se o usuário está logado ou não.

Também criaremos upload de arquivos, clicando no ícone de filmadora no canto superior direito, seremos redirecionados para uma tela para enviarmos um vídeo.

Na tela seguinte, intitulada "Envie um vídeo!", temos um campo para inserir o link chamado "Link embed" (em português, "Link incorporado") e o outro campo chamado "Título do vídeo" para inserirmos o título que desejamos colocar no vídeo. Abaixo, temos um botão "Escolher arquivo" para colocarmos uma imagem do vídeo.

Ao subirmos um vídeo novo para o sistema, podemos escolher uma imagem ou capa para ele. Dentro desses conceitos, falaremos bastante sobre segurança.

Teremos um capítulo específico sobre segurança. Podendo ser sobre a segurança das senhas, das sessões ou de upload ("envio") de arquivos.

Iniciaremos pelo básico, compreendendo as boas práticas e depois nos aprofundamos na parte de segurança. Após isso, veremos como desenvolver uma API. Conseguiremos acessar o sistema como se ele fosse uma API, usando o JSON na resposta.

Vamos aprender como ler o conteúdo de uma requisição que chega no formato .json e como retornar o status do retorno de forma mais customizada. Vamos entender um pouco sobre essa área de APIs Web, também.

Caso tenha alguma dúvida ao longo deste curso, sinta-se à vontade para abrir um tópico no fórum, ou acessar o nosso servidor no Discord para tirar suas dúvidas ou sugestões.

No próximo vídeo, criaremos uma tabela de usuários e aprenderemos como vamos armazenar as senhas. Isto é, como desenvolver essas funcionalidades no sistema.

Te espero no próximo vídeo. Até mais!

Segurança com autenticação - Criando a tabela users

Olá! Vamos iniciar a implantação da autenticação dos usuários no sistema.

No canto superior direito da tela inicial do sistema, temos o botão "Sair". Atualmente, como reestruturamos o projeto, esse botão nos redireciona para a página errada. Ao clicarmos no botão, recebemos a seguinte mensagem de erro:

NOT FOUND (em português, "Não encontrado")

The request resource /pages/login.html was not found on this server. (em português, "O recurso de solicitação /pages/login.html não foi encontrado neste servidor")

Podemos voltar para a página inicial, clicando no botão de voltar no canto superior esquerdo. O que desejamos fazer é implementar uma forma para identificarmos os usuários.

Por exemplo, vamos supor que temos um formulário de login, em que é necessário preencher o e-mail e senha para acessar ao sistema. Se os dados informados estiverem corretos, o usuário é redirecionado para a página inicial do sistema. Caso contrário, o usuário não acessa o sistema.

Portando, o nosso primeiro passo é autenticar o usuário. Ou seja, reconhecer o usuário como quem ele informa que é, garantindo que o usuário que está tentando entrar no sistema é, de fato, o dono do e-mail.

Iniciaremos a implantação disso com a parte do banco de dados. Criaremos uma tabela no banco de dados para lidar com os usuários. Em uma aplicação mais profissional, usamos o conceito de migrations (você vai aprender sobre esse conceito ao estudar sobre frameworks). Mas para o nosso cenário, faremos de forma manual.

Se tivermos uma forma de nos conectarmos ao banco de dados, basta nos conectarmos e rodar o comando create table. No caso, para não precisarmos instalar e configurar o banco de dados, criaremos um arquivo PHP.

Para isso, do lado esquerdo da IDE do projeto, selecionaremos o pacote aluraplay e clicamos com o botão direito do mouse. Será exibido um menu, em que escolhemos as opções "New > PHP file".

No pop-up seguinte, intitulado "Create New PHP File" (em português, "Criar Novo Arquivo PHP"), temos dois campos: "file name" e "file extension". Abaixo, dois botões: "Ok" e "Cancel".

No campo "file name" digitaremos "tabela-usuario" e em "file extension" deixaremos "php".

File name: tabela-usuario
File extension: php

Em seguida, clicamos no botão "Ok". Seremos redirecionados para o arquivo e note que à esquerda do projeto, temos um arquivo em .php chamado tabela-usuario.php.

tabela-usuario.php:

<?php

declare(strict_types=1);

Agora, vamos em "public > index.php" para copiarmos o trecho do código referente à conexão com o PDO.

Trecho do código do arquivo index.php para copiar:

$dbPath = __DIR__ . '/../banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

Após copiar, voltaremos ao arquivo tabela-usuario.php e colamos na linha seguinte do declare().

tabela-usuario.php:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/../banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

Depois de colar, precisamos ajustar o caminho para nos conectarmos ao banco de dados correto. Removeremos o ../, para acessar o banco de dados na mesma pasta.

tabela-usuario.php:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

Já que estamos conectados, podemos usar o método exec para criar a tabela de usuários ("users"). Na última linha, vamos colocar $pdo -> exec() e a função create table dentro do parêntese.

tabela-usuario.php:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$pdo->exec('CREATE TABLE users();');

Quais as informações que desejamos visualizar nesta tabela users? Queremos visualizar o ID, que será um inteiro e a nossa chave primária (o que identifica um usuário); teremos o e-mail em formato de texto, e depois um hash de senha.

Isto é, em password não armazenaremos a senha de forma explícita. Vamos entender melhor sobre o hash e a sua importância para a segurança de senhas mais para frente.

tabela-usuario.php:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT, password TEXT);');

Assim, estamos criando um usuário que será identificado por um ID, que terá um e-mail e uma senha. Por enquanto, são essas as informações que precisamos.

Agora, abriremos o terminal e rodamos o comando php tabela-usuario.php.

php tabela-usuario.php

Não retornou nada, somente o símbolo de cifrão "$". Isso significa que deu certo! Com isso, a tabela está pronta para armazenarmos os usuários e seus respectivos dados nela.

Mas voltando à questão da senha: vamos armazená-la em texto explícito? Outro detalhe: teremos um formulário de registro de usuários, ou faremos isso em outro lugar? Perceba que temos vários detalhes para debatermos ainda.

No próximo vídeo, aprenderemos como armazenar os usuários na tabela. Até mais!

Segurança com autenticação - Armazenando senhas

Olá! Na aula anterior, criamos a tabela de users. Porém, podemos remover o arquivo tabela-usuario.php, já que ele não faz parte do projeto.

A partir de agora, vamos entender como criar um usuário. Nos sites, é comum termos uma página para nos registrar, em que há um formulário que preenchemos os dados e criamos a conta.

No entanto, é comum existir alguma área restrita nos sistemas, sendo onde se registra os usuários. Ou, às vezes, quando acabamos de gerar um sistema adicionamos um usuário administrador, com todas as permissões do banco de dados.

Há diversas maneiras de incluirmos usuários, mas todas elas executam o código em PHP. Portanto, ao invés de gerarmos um formulário, ou sessão administrativa, criaremos um arquivo em que vamos gerar o usuário pela linha de comando.

Na IDE, selecionamos a pasta aluraplay e clicamos com o botão direito do mouse. Nas opções do menu exibido, escolhemos as "New > PHP Class".

No pop-up, intitulado "Create New PHP File", temos dois campos: "file name" e "file extension". Abaixo, dois botões: "Ok" e "Cancel". No campo "file name" digitaremos "cria-usuario" e em "file extension" vamos deixar como "php".

File name: cria-usuario
File extension: php

Logo após, clicamos no botão "Ok". Seremos redirecionados para o arquivo criado e note que à esquerda do projeto, temos um arquivo em .php chamado cria-usuario, na raiz do projeto.

cria-usuario:

<?php

declare(strict_types=1);

Neste arquivo, vamos entender como armazenar usuários com suas respectivas senhas, no banco de dados. Novamente copiaremos o trecho do código referente à conexão ao banco de dados, no arquivo index.php.

Trecho do código copiado no arquivo index.php:

//código omitido

$dbPath = __DIR__ . '/../banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

//código omitido

Agora, voltaremos ao arquivo cria-usuario e colaremos o trecho copiado.

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/../banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

Ajustaremos o caminho do banco de dados, removendo o /...

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

No banco de dados, desejamos inserir na tabela users os campos de e-mail e senha, com determinados valores. Portanto, usamos o $sql = 'INSERT INTO users (email, password) VALUES (?, ?);';.

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

No entanto, armazenar a senha pode ser um processo crítico. Vamos supor que alguém possua acesso ao nosso servidor e consegue visualizar os dados do banco de dados. Ou, mesmo sem acesso ao servidor a pessoa consiga expor o conteúdo da tabela users.

Com isso, essa pessoa terá acesso às senhas dos usuários. Isso é um problema de segurança. Para ajustarmos, temos algumas possibilidades, dentre elas é pegar a senha e passá-la por algum algoritmo de encriptação, para que a senha não fique explícita para qualquer pessoa que entrar no banco de dados.

Porém, algoritmos de cifras nos permite cifrar o dado e depois decifrar. Vamos supor que algum atacante teve acesso aos dados e decifrou a senha, ele não consegue visualizar a senha de forma explícita.

Por exemplo, se a senha é "123456" e ao cifrarmos vira a string "yvDJAKFHDHNUININDIHN", o atacante terá acesso ao texto explícito, sendo o "123456". A partir disso, ele pode conseguir decifrar todas as outras senhas dos outros usuários.

Além disso, podemos ter alguém mal intencionado na equipe que conseguir decifrar as senhas. Portanto, essa maneira não é tão interessante para nós. O ideal é ninguém nunca ter acesso às senhas.

Por isso, executaremos o algoritmo de hash, que faz parte da criptografia, mas não é uma encriptação. Como no exemplo anterior, uma senha "123456" irá virar uma string aleatória, mas a diferença é que algoritmos de hash são irreversíveis. Isto é, não conseguimos decifrar.

Quando o usuário for logar, como vamos saber se a senha dele é "123456"? O que faremos é: pegar o resultado (no caso, "123456"), aplicar o hash novamente e compará-lo com o hash que já está armazenado no banco de dados referente à essa senha.

Usaremos o algoritmo de hash para armazenarmos as senhas dos nossos usuários na tabela users. Caso queira entender melhor, acesse o nosso Para saber mais: criptografia.

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

Agora, vamos implementar. Iniciaremos capturando o e-mail, logo após o pdo, inserindo $email = '';.

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$email = '';

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

Na linha seguinte, pegamos a senha.

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$email = '';
$password = '';

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

Podemos incluir valores fixos no e-mail e na senha, no entanto, queremos receber esses dados pelo terminal. Portanto, abriremos o terminal e, vamos permitir a chamada do comando php cria-usuario.php passando o e-mail e uma senha.

php cria-usuario.php "email@example.com" "123456"

Desejamos permitir essa possibilidade. O PHP nos permite capturar os valores passados pelo terminal, e os insere em uma variável no código chamada $argv[]. Como vamos pegar o primeiro parâmetro, dentro dos colchetes inserimos o número 1.

$email = $argv[1];

Aplicamos a mesma lógica para a senha, como segundo parâmetro.

$email = $argv[1];
$password = $argv[2];

Assim, o código completo do arquivo cria-usuario até este momento é:

cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$email = $argv[1];
$password = $argv[2];

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

Sabemos que não adicionaremos a senha (password) no segundo parâmetro dos valores, em VALUES (?, ?). Primeiro, aplicamos o hash nela. Para isso, após a senha, colocamos um $hash =. Para pegarmos o hash, precisamos rodar alguma função.

$hash = hash()

Em PHP, temos a função hash, porém precisaríamos informar qual o algoritmo. E os algoritmos usados nessa função hash do PHP não são para hash de senha. Há algoritmos de hash focados em agilidade, em que a ideia central deles é verificar a integridade dos dados.

Vamos supor que baixamos um arquivo grande da Internet, que pode ser corrompido no caminho. No site que baixamos o arquivo, nos é fornecido um hash, e ao fazermos o download podemos fazer o hash no computador e compará-los. Com isso, garantimos que o arquivo não foi corrompido.

É para isso que serve a função hash. Inclusive, pode ser que você já tenha visto em algum lugar recomendando o uso do algoritmo md5(). Caso sim, pode fechar o site, ele não é atualizado.

$hash = md5()

Isso porque essa função não é tão segura e não deve ser usada para armazenar senhas. Até em cenários de integridade de dados, ela não é mais recomendada. A função md5 existe, mas está longe de ser a melhor possibilidade para o nosso caso. E nunca deve ser usada para armazenar senhas.

Para este cenário, usaremos uma função chamada password_hash().

$hash = password_hash();

Esta função espera alguns parâmetros, sendo o primeiro a senha em que aplicamos o hash. Portanto, dentro do parêntese passamos como primeiro parâmetro o $password.

$hash = password_hash($password);

Depois, precisamos passar qual algoritmo usaremos para aplicar o hash. Caso não entenda muito bem esse assunto, você pode usar o password_default (em português, "senha padrão").

$hash = password_hash($password, PASSWORD_DEFAULT);

É um algoritmo padrão que pode ser alterado ao longo do tempo. No momento de gravação deste vídeo, o algoritmo padrão usado é o bcrypt. Mas neste curso, usaremos o algoritmo Argon2.

$hash = password_hash($password, PASSWORD_ARGON2ID);

Com isso, ficamos com o seguinte código no arquivo cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$email = $argv[1];
$password = $argv[2];
$hash = password_hash($password, PASSWORD_ARGON2ID);

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';

Após incluirmos o hash de senha, vamos executar a inserção desse usuário no banco de dados. Com o pdo, vamos realizar o prepare do SQL, e isso nos devolve um statement.

$statement = $pdo->prepare($sql);

A partir desse statement, executaremos o bindValue do primeiro e do segundo parâmetro, sendo respectivamente, e-mail e hash. Não será armazenada a senha em texto aberto, mas sim o hash.

$statement = $pdo->prepare($sql);
$statement->bindValue(1, $email);
$statement->bindValue(2, $hash);

Por fim, executamos o statement.

$statement = $pdo->prepare($sql);
$statement->bindValue(1, $email);
$statement->bindValue(2, $hash);
$statement->execute();

Código completo do arquivo cria-usuario:

<?php

declare(strict_types=1);

$dbPath = __DIR__ . '/banco.sqlite';
$pdo = new PDO("sqlite:$dbPath");

$email = $argv[1];
$password = $argv[2];
$hash = password_hash($password, PASSWORD_ARGON2ID);

$sql = 'INSERT INTO users (email, password) VALUES (?, ?);';
$statement = $pdo->prepare($sql);
$statement->bindValue(1, $email);
$statement->bindValue(2, $hash);
$statement->execute();

Agora, vamos verificar o que isso nos retorna. No terminal, executaremos novamente o seguinte comando:

php cria-usuario.php "email@example.com" "123456"

Em um sistema real, o ideal seria solicitar senhas mais seguras do que 123456. Não faremos isso porque o foco deste curso não é sobre segurança.

Ao teclarmos "Enter" para executar, não teremos nenhum retorno. Isso significa que deu certo! A partir disso, podemos acessar o banco de dados e verificar como essas senhas foram armazenadas.

Para isso, usamos o comando sqlite para acessar o banco de dados, informando o banco.

sqlite3 banco.sqlite

Como retorno, obtemos:

SQLite version 3.31.1 2020-01-27 19:55:54

Enter ".help" for usage hints.

E note que agora estamos logados como sqlite, dentro do banco de dados. Agora, selecionaremos todas as informações da tabela users, usando o comando select.

SELECT * FROM users;

Como retorno das informações da tabela users, obtemos:

1|email@example.com|$argon2id$v=19$m=65536,t=4,p=1$WTR0eTJXTGZuVHFlVkljOQ$DBXlhL9bjlcVLJ4kNb8J+6pLjZonjr20qS6V2X2mZJY

Temos o ID 1, o e-mail que inserimos e depois como a senha foi armazenada. Note que temos algumas informações nessa string salva, como qual o algoritmo, o valor v, m, t e p - que nem precisamos saber.

Após esses valores, temos o hash de senha. Isso significa que além da senha, essa string contém outras informações para conseguirmos comparar o hash.

Mas precisamos saber todas essas informações ou, podemos comparar o hash através do código? Passando que o hash é igual ao password hash.

cria-usuario

// código omitido

$hash === password_hash();

Isso não funciona, vamos verificar o motivo. No terminal, fecharemos o sqlite usando o comando .quit.

.quit

Em seguida, abriremos o terminal interativo do PHP com o comando php -a.

php -a

No retorno, nos informa que o modo interativo foi ativado:

Interactive mode enabled

Dentro do terminal interativo do PHP, exibiremos um password hash da senha 123456, usando o algoritmo argon2id.

echo password_hash('123456', PASSWORD_ARGON2ID);

Ao executarmos o comando, obtemos:

$argon2id$v=19$m=65536,t=4,p=1$VVJNUlloVHRvTzNQWlBYNw$wVyy8Xxtdf4NACprDgXDFrx1aMi9fjC4Mb8H6CTdzjs

Perceba que o hash que acabamos de gerar difere do anterior. Se rodarmos novamente o mesmo comando, será gerado outro hash. Isto é, cada vez que a função for executada, um novo hash será gerado.

Assim, caso um atacante descubra a senha de um dos usuários do sistema, ele não consegue identificar as outras senhas. Vamos supor que o usuário deixou vazar a senha 123456, deste modo, se o atacante conseguir acesso às senhas, não conseguirá identificar se há mais alguém com a senha 123456. Isso porque cada usuário terá um valor diferente no hash.

Por isso, precisamos usar outra função para verificar se a senha está correta. É isso que aprenderemos no próximo vídeo, enquanto implementamos o formulário de login.

Te espero na próxima aula!

Sobre o curso PHP na Web: lidando com segurança e API

O curso PHP na Web: lidando com segurança e API possui 144 minutos de vídeos, em um total de 50 atividades. Gostou? Conheça nossos outros cursos de PHP em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda PHP acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas