O que você precisa saber antes de começar a desenvolver aplicações front-end?

O que você precisa saber antes de começar a desenvolver aplicações front-end?

Olá leitor, seja bem vindo ao portal da micilini, e hoje você vai aprender tudo o que você precisa saber antes de começar a desenvolver suas aplicações front-end.

Mas antes de começarmos, é importante que você entenda alguns tópicos, buscando responder a seguinte pergunta: O que eu preciso saber antes de começar a desenvolver aplicações front-end utilizando o ReactJS/AngularJS e entre outros frameworks?

Aplicações Front-End são consideradas aplicações Modulares

Recentemente aqui na micilini, eu postei um artigo que fala sobre a diferença entre aplicações monolíticas e aplicações modulares.

Onde lá, foi dito que nos primordios do desenvolvimento web, todas as aplicações eram construídas de maneira monolítica, ou seja, um único projeto armazenava toda a lógica back-end e front-end dentro de uma única pasta.

Com o passar dos anos, novas arquiteturas e padrões de software foram surgindo, até que chegamos em um novo padrão onde a lógica do sistema ficava de um lado, e a parte do layout do site ficava de outro.

Surgindo assim a era das aplicações modulares, onde o o back-end da aplicação fica totalmente separado do front-end.

Isso possibilitou que os desenvolvedores pudessem criar tanto o front-end quanto o back-end por meio de diversas linguagens e framerworks diferentes, onde todas as aplicações pudessem se comunicar utilizando o protocolo universal HTTP, por meio de dados em JSON ou XML.

Pois bem, sabendo disso, é importante que você saiba que existem algumas regrinhas de ouro quando você atua como desenvolvedor Front-End.

Aplicações front-end sempre depende de uma aplicação back-end

Sim, e você não leu errado!

Ao menos que você esteja criando uma aplicação super simples (site informativo), em 95% das vezes uma aplicação front-end vai depender de um back-end por trás.

Front-end e Back-end são duas faces de uma mesma moeda, apesar de agora (aplicações modulares) estarem em pastas diferentes (ou até mesmo em servidores diferentes).

Uma aplicação front-end sempre depende de um back-end por trás, isto para que ele possa oferecer todas as informações para que o front-end possa continuar o seu fluxo.

Observação: Se a sua aplicação front-end não depende de qualquer tipo de back-end, cuidado, ela pode estar insegura!

Em segunda lugar, toda a lógica de uma aplicação front-end sempre vai estar exposta para o usuário atráves do DOM DOCUMENT

E isso significa que um usuário mais avançado de internet, pode ir no botão direito do mouse (famoso inspecionar elemento), e de lá ter acesso a toda a sua lógica da aplicação, onde ele pode ver as rotas, ver os arquivos (scripts) que são carregados, e até mesmo ignorar algumas regras da sua aplicação.

Mas calma! O usuário não consegue alterar a lógica da sua aplicação, somente a versão que está no computador dele. Esse processo é explicado em mais detalhes na lição 1 da jornada Javascript, onde eu explico como funciona o processo de uma requisição web.

Sendo assim, NUNCA mas NUNCA armazene qualquer tipo de informação sensível em uma aplicação front-end. Pois essas informações sempre devem vir e estar armazenadas dentro de uma aplicação back-end - vai por mim, é mais seguro!

Lá no artigo que fala sobre aplicações monolíticas e modulares, eu citei um exemplo de uma aplicação do Clube do Bolinha, no qual o front-end estava armazenando uma informação sensível dentro da tela, o que a deixava exposta para usuários mais avançados de internet.

Repetindo, nunca faça isso, pois é considerado uma falha na aplicação 🔓

12 dicas de segurança para você aplicar em suas aplicações front-end (e back-end)

Daí voce pode me perguntar: "Beleza, então quais são as dicas que você pode me dar? E o que eu tenho que tomar cuidado ao criar aplicações front-end?".

Visando em responder essas perguntas, separei em 12 tópicos algumas dicas de segurança que você deve ter em mente ao criar aplicações front-end, de resto você pode aprender mais aqui no Portal da Micilini 🙂

1) Validações de Input

Todo e qualquer input (formulário) deve possuir uma validação rigorosa, de modo a previnir e evitar que dados maliciosos invadam a sua aplicação e consequentemente façam a festa lá dentro.

Sabe aquele <input type="text"...> que armazena o nome ou o login do seu usuário? Sabe aquele <select...> que armazena uma lista, ou aquele <input type="file"...> que armazena um arquivo?

Então... qualquer tipo de input deve ter uma validação por trás.

Não só aquelas validações como:

  • required
  • pattern
  • min
  • max
  • ...

Como também subvalidações que são executadas pelos services da sua aplicação antes de serem enviados ao back-end (APIs específicas).

Por exemplo, vamos supor que temos o seguinte formulário com apenas dois campos de login e senha:

<form method="post" class="formLogin">
 <input type="text" name="login" placeholder="Digite seu Login" pattern="{5,20}" title="Seu Login deve ter entre 5 a 20 caracteres" required />
 <input type="password" name="senha" placeholder="Digite sua Senha" pattern="{3,100}" title="Sua Senha deve ter entre 3 a 100 caracteres" required />
 <button type="submit" onclick="validarFormulario()">Acessar</button>
</form>

Como vemos, alguns atributos como pattern e o required, adicionam uma certa segurança aos inputs. Mas como qualquer usuário pode desativar isso em seu navegador, você não pode só depender desses atributos.

Também é recomendável que você refaça essa validações (ainda no front-end da aplicação, antes de envia-las para algum back-end) logo após o usuário clicar no botão de "Acessar"

function validarFormulario() {
 var login = document.querySelector('input[name="login"]').value;
 var senha = document.querySelector('input[name="senha"]').value;

 // Validando o campo de login
 if (login.length < 5 || login.length > 20) {
 alert("Seu Login deve ter entre 5 a 20 caracteres.");
 return;
 }

 // Validando o campo de senha
 if (senha.length < 3 || senha.length > 100) {
 alert("Sua Senha deve ter entre 3 a 100 caracteres.");
 return;
 }

 // Se passou pela validação, pode prosseguir com o envio do formulário
 // Aqui você pode adicionar o código para enviar os dados para o servidor
 alert("Formulário validado com sucesso! Login: " + login + ", Senha: " + senha);
 }
}

Note que estou validando cada um dos campos, a fim de verificar se eles seguem os padrões definidos pelo HTML.

Por fim, do lado do back-end é recomendável que você faça novamente esse tipo de verificação, a fim de garantir que os dados seguem os padrões estabelecidos pelo front-end, e também pelos padrões do seu negócio.

Exemplo de uma aplicação simples (back-end) em PHP recebendo esses dados e tratando da maneira correta:

<?php
 // Processar os dados do formulário quando o formulário for enviado
 if ($_SERVER["REQUEST_METHOD"] == "POST") {
 $login = $_POST['login'];
 $senha = $_POST['senha'];

 // Validando o campo de login
 if (strlen($login) < 5 || strlen($login) > 20) {
 echo "<p>Seu Login deve ter entre 5 a 20 caracteres.</p>";
 }

 // Validando o campo de senha
 if (strlen($senha) < 3 || strlen($senha) > 100) {
 echo "<p>Sua Senha deve ter entre 3 a 100 caracteres.</p>";
 }

 // Se os campos passaram na validação, você pode adicionar o código para processar os dados
 // E enviar uma resposta de volta ao front-end da aplicação 
 }
?>

Resumindo, não se esqueça de limpar as entradas do usuário para se proteger contra ataques de Cross-Site Scripting (XSS) e SQL Injection.

2) Content Security Policy (CSP)

Apesar dessa dica estar mais voltada para o back-end do que para o front-end, é bom nos atentarmos também a ela.

A Content Security Policy (CSP) é um mecanismo de segurança que ajuda a proteger um site contra vários tipos de ataques, como XSS (Cross-Site Scripting), clickjacking e outros ataques de injeção de código.

É por meio dela que conseguimos controlar que tipos de recursos devem ser carregados por um navegador, o que limita as fontes de onde esses recursos podem vir, ajudando a reduzir a superfície de ataque além de mitigar os riscos de segurança.

O CSP nada mais é do que uma política de segurança que implementamos  através de um cabeçalho HTTP chamado Content-Security-Policy ou Content-Security-Policy-Report-Only.

Por exemplo, com o CSP podemos definir que apenas scripts provenientes do próprio domínio são permitido. Vejamos como aplicar esse conceito utilizando NodeJS (Javascript) atuando como uma aplicação back-end.

// Configurando a CSP no cabeçalho da resposta HTTP
const http = require('http');

const server = http.createServer((req, res) => {
 // Definindo a CSP no cabeçalho de resposta
 res.setHeader('Content-Security-Policy', "default-src 'self'");

 // Enviando uma resposta simples
 res.writeHead(200, {'Content-Type': 'text/html'});
 res.write('<h1>Eu aceito essa requisição!</h1>');
 res.end();
});

// Escutando na porta 3000
server.listen(3000, () => {
 console.log('Servidor rodando em http://localhost:3000');
});

No caso do comando acima, estamos definindo as políticas de CSP dentro do método res.setHeader(). Onde estamos permitindo apenas recursos do próprio domínio ('self').

Nesse caso, o script acima só irá aceitar recursos (dados via método POST) se, e somente se o remetente for do próprio dominio.

Dessa forma, se um usuário tentar injetar um script malicioso em um campo de entrada em um formulário, o navegador não executará esse script se ele for proveniente de um domínio diferente do próprio site.

Por exemplo, vamos supor que você possui o domínio https://meusite.com, e que na página principal você tenha um formulário de login:

<form class="loginForm" method="post" action="https://meusite.com/loginUser">
 <input type="text" name="login" placeholder="login" required>
 <input type="password" name="password" placeholder="senha" required>
</form>

Como visto no código acima, esse formulário envia uma requisição para a URL login que existe dentro do seu domínio (action="https://meusite.com/loginUser").

Que por sua vez, executa aquele código em NodeJS que vimos anteriormente.

No caso deste exemplo, o NodeJS irá processar a requisição, uma vez que o Content-Security-Policy está setado para self, ou seja, ele está permitindo scripts que existem no mesmo domínio.

Agora, supondo que seu amigo possui o domínio https://sitedoamigo.com, e que ele esta tentando fazer um login no seu domínio (https://meusite.com), o CSP irá bloquear essa requisição, uma vez que ao nosso código back-end (em NodeJS) tem uma política que só permite receber scripts do meu domínio (https://meusite.com) e não de terceiros.

Ao menos que, você defina as URLs permitidas dentro do CSP da seguinte forma:

res.setHeader('Content-Security-Policy', "default-src 'self' https://meusite.com https://sitedoamigo.com https://micilini.com;");

Note que estamos aceitando scripts advindos dos sites meusegundosite.com e micilini.com. Lenbrando que você pode permitir quantos sites desejar.

3) Comunicação Segura (HTTPs)

Sempre faça o uso do HTTPS (cadeado verde na URL do navegador) para criptografar dados transmitidos entre o cliente e o servidor.

Certifique-se de que os endpoints da API e os recursos externos também estejam protegidos com HTTPS.

Lembre-se de implementar certificados HTTPS (válidos) tanto nas URLs do seu back-end, quanto nas URLs das suas aplicações front-end.

Um certificado gratuito muito bom é o Lets Encrypt, e assim como os outros, ele deve ser configurado internamente no seu servidor.

É importante ressaltar que os certificados HTTPs são configurados no servidor e não dentro da aplicação back-end ou front-end.

4) Cross-Origin Resource Sharing (CORS)

É sempre bom configurar cabeçalhos CORS em seu servidor, isso para evitar que domínios sem permissão, possam acessar recursos da sua aplicação front-end, como imagens, arquivos CSS, arquivos Javascript e entre outros.

Isso mitiga ataques de falsificação de solicitação de origem cruzada (famoso CSRF) e também a inclusão de scripts entre sites (XSSI).

Basicamente o CORS pode impedir que por exemplo, o seu logotipo em SVG, ou uma imagem que existe dentro do seu site, seja carregada em outro site e usada por ele, a menos que você tenha configurado o CORS para permitir explicitamente esse tipo de acesso.

Apesar do CORS ser configurado do lado do servidor, se você estiver usando NodeJS como back-end, você configurará o CORS de lá da seguinte forma:

1) Instale o pacote de CORS na sua aplicação:

npm install cors

ou 

yarn add cors

2) Em seguida basta adicionar o pacote na sua aplicação:

const express = require('express');
const cors = require('cors');

const app = express();

// Configurando o CORS para permitir solicitações de qualquer origem
app.use(cors());

// Outras configurações e rotas da sua aplicação...

// Iniciando o servidor na porta 3000
app.listen(3000, () => {
 console.log('Servidor rodando em http://localhost:3000');
});

Neste exemplo, o comando app.use(cors()) configura o CORS para permitir solicitações de qualquer origem.

Mas você também pode configurar o CORS para permitir apenas solicitações de origens específicas, como por exemplo:

app.use(cors({ origin: 'https://seusite.com' }));//Para uma única origem

ou 

app.use(cors({
 origin: ['https://origem1.com', 'https://origem2.com', 'https://origem3.com']
}));//Para mais de uma origem

5) Autenticação e Autorização

Você deve implementar mecanismos seguros de autenticação e autorização de usuários, e isso inclui: 

Uso de senha segura: Faça o uso de algoritmos de hash seguros (como bcrypt) para armazenar senhas no banco de dados. Nunca armazene senhas em texto simples.

Tokens de Autenticação: Utilize tokens de autenticação, como JSON Web Tokens (JWT), para autenticar usuários. Eles são assinados digitalmente e podem conter informações sobre o usuário e suas permissões.

Mecanismos de Login Seguros: Implemente mecanismos de login seguros, como HTTPS, para proteger as credenciais do usuário durante a transmissão.

Controle de Acesso Baseado em Função (RBAC): Implemente um sistema de controle de acesso baseado em funções para definir quais usuários têm permissão para acessar recursos específicos da aplicação.

Verificação de Permissões: Antes de conceder acesso a recursos protegidos, verifique se o usuário possui as permissões adequadas para realizar a ação desejada.

Princípio do Privilégio Mínimo: Conceda aos usuários apenas as permissões necessárias para realizar suas tarefas. Evite conceder acesso excessivo.

Em aplicações front-end mais robustas (de grandes empresas), o uso do RBAC e de tokens JWT se faz presente na maioria delas.

6) Nunca armazene informações confidenciais no front-end

Como falado anteriormente, nunca codifique informações confidenciais como chaves de API ou senhas diretamente no seu código font-end.

Lembra da aplicação em ReactJS do clube do bolinha? Então, nunca faça aquilo!

7) Atualize constantemente suas bibliotecas

Em aplicações feitas com ReactJS, AngularJS... tanto quanto aplicações feitas em Laravel, NodeJS... cada vez mais dependem de bibliotecas externas para facilitar o nosso desenvolvimento.

Neste caso, é sempre bom manter todas essas bibliotecas atualizadas para corrigir vulnerabilidades de segurança.

Apesar disso, ferramentas como npm costumam avisar falhas de segurança e possíveis correções.

Portanto, é sempre bom estar atento a isso.

8) Roubo de sessões

Não deixe de implementar práticas seguras relacionadas ao gerenciamento de sessões e cokies, e isso inclui garantir que eles:

  • Possuam um tempo de vida limitado.
  • São seguros.
  • Tenham seus acessos limitados.
  • E que só possam ser enviados via HTTPS.

Evitando assim que usuários mal intencionados roubem os dados da sua sessão.

Recetemente eu escrevi um artigo muito interessante que trata sobre esses assuntos: Cookies VS LocalStorage: Dicas de como armazenar dados e tokens (JWT) com segurança.

9) Tratamento de Erros

Sabe aqueles estruturas de try...catch da sua aplicação, onde o catch trata os erros que são gerados?

Então, evite expor informações confidenciais em mensagens de erro.

Sendo assim, personalize as mensagens de erro de modo a exibir mensagens mais simples de se usar sem revelar informações apresentadas do erro.

Ou seja, em vez de você mostrar o seguinte erro para o usuário: "error when try to insert a column usuario_login in table usuarios not found", que a princípio já revela alguns detalhes da sua aplicação, opte por mostrar: "An error ocurred in application [bg665t]".

Eu particularmente adoro atribuir um código de erro diferente (bg665t), pois dessa forma eu sei o que está acontecendo e onde procurar por uma solução.

10) Faço o uso de tokens Anti-CSRF

Uma outra tática de segurança é fazer o uso de tokens anti-CSRF em todas as suas solicitações para evitar ataques de falsificação de solicitação entre sites.

Por exemplo, vamos supor que você deseja implementar tokens anti-CSRF tanto em sua aplicação back-end quanto front-end.

Supondo que você esteja fazendo sua aplicação usando NodeJS (back-end), você pode criar suas rotas que serão consumidas pelo front-end da seguinte forma:

const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');

const app = express();

// Middleware para analisar cookies do cliente
app.use(cookieParser());

// Middleware para proteção CSRF
const csrfProtection = csrf({ cookie: true });

// Rota para gerar e enviar o token CSRF para o cliente
app.get('/csrf-token', csrfProtection, (req, res) => {
 res.json({ csrfToken: req.csrfToken() });
});

// Rota protegida que requer o token CSRF
app.post('/api/data', csrfProtection, (req, res) => {
 // Validar o token CSRF antes de processar a requisição
 res.json({ message: 'Requisição protegida por CSRF bem-sucedida!' });
});

// Outras rotas e configurações da sua aplicação...

// Iniciando o servidor na porta 3000
app.listen(3000, () => {
 console.log('Servidor rodando em http://localhost:3000');
});

Note que foi criado uma rota chamada '/csrf-token' responsável por gerar e enviar o token CSRF para o cliente.

Supondo que a nossa aplicação front-end vá fazer o consumo desse token, e que ela foi feita em ReactJS:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
 const [token, setToken] = useState('');

 useEffect(() => {
 // Função para obter e armazenar o token CSRF do servidor
 const fetchCsrfToken = async () => {
 try {
 const response = await axios.get('http://seuservidor.com/csrf-token');
 const csrfToken = response.data.csrfToken;
 setToken(csrfToken);
 } catch (error) {
 console.error('Erro ao obter o token CSRF:', error);
 }
 };

 fetchCsrfToken();
 }, []);

 const handleFormSubmit = async (event) => {
 event.preventDefault();

 // Enviar requisição com o token CSRF
 try {
 await axios.post('http://seuservidor.com/api/data', { data: 'dados' }, {
 headers: {
 'X-CSRF-Token': token // Incluir o token CSRF no cabeçalho da requisição
 }
 });
 console.log('Requisição bem-sucedida!');
 } catch (error) {
 console.error('Erro ao enviar requisição:', error);
 }
 };

 return (
 <div>
 <h1>Exemplo de Token Anti-CSRF em React.js</h1>
 <form onSubmit={handleFormSubmit}>
 {/* Outros campos do formulário */}
 <button type="submit">Enviar</button>
 </form>
 </div>
 );
}

export default App;

Note que antes de enviarmos o formulário, o useEffect se responsabiliza por pegar o token CSRF antes de qualquer requisição HTTP. Note também que o token retornado é enviado de volta ao back-end assim que o usuario envia o formulário.

11) X-Content-Type-Options, X-Frame-Options e X-XSS-Protection

Para aumentar a segurança das suas aplicações front-end, você precisa definir certas configurações como: X-Content-Type-Options, X-Frame-Options e X-XSS-Protection do lado do servidor.

É usada para controlar o comportamento do navegador em relação à interpretação do tipo de conteúdo de uma resposta HTTP.

O valor recomendado é X-Content-Type-Options: nosniff, que instrui o navegador a não alterar automaticamente o tipo de conteúdo da resposta.

É usada para controlar se o navegador deve permitir que uma página seja renderizada dentro de um <frame>, <iframe>, <embed>, ou <object>.

O valor recomendado é X-Frame-Options: DENY, que instrui o navegador a não renderizar a página em nenhum contexto de frame.

É usada para ativar ou desativar o filtro XSS (Cross-Site Scripting) embutido nos navegadores.

O valor recomendado é X-XSS-Protection: 1; mode=block, que ativa o filtro XSS e instrui o navegador a bloquear a renderização da página se detectar um ataque XSS.

É importante ressaltar que você precisaria configurar esses cabeçalhos no servidor que hospeda sua aplicação, que pode ter sido feita em React.js, Node.js, Python, PHP, entre outros.

E que dependendo da tecnologia do servidor, a maneira de configurar esses cabeçalhos podem variar de uma linguagem para outra.

Abaixo está um exemplo de uma aplicação feita em NodeJS, que faz o uso dos cabeçalhos vistos acima:

const express = require('express');
const app = express();

// Configuração do X-Content-Type-Options
app.use((req, res, next) => {
 res.header('X-Content-Type-Options', 'nosniff');
 next();
});

// Configuração do X-Frame-Options
app.use((req, res, next) => {
 res.header('X-Frame-Options', 'DENY');
 next();
});

// Configuração do X-XSS-Protection
app.use((req, res, next) => {
 res.header('X-XSS-Protection', '1; mode=block');
 next();
});

// Outras configurações e rotas da sua aplicação...

// Iniciando o servidor na porta 3000
app.listen(3000, () => {
 console.log('Servidor rodando em http://localhost:3000');
});

12) Defina limites de acessos

Implemente a limitação de taxa para restringir o número de solicitações que um usuário pode fazer em um determinado período, o que ajuda a proteger contra ataques de força bruta.

Também é legal implementar o Google Recaptcha e os serviços da CloudFlare, pois ambos consegue inibir diversos ataques de bots, principalmente ataques de força bruta.

Caso preferir, você também pode fazer isso dentro da sua aplicação back-end usando a biblioteca express-rate-limit, caso estiver utilizando o NodeJS:

1) Instale a biblioteca de rate limit na sua aplicação back-end:

npm install express-rate-limit

ou

yarn add express-rate-limit

2) Implemente a biblioteca no seu código:

const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

// Middleware de rate limit
const limiter = rateLimit({
 windowMs: 15 * 60 * 1000, // 15 minutos
 max: 100, // limite de 100 requisições por IP
 message: 'Limite de solicitações excedido, tente novamente mais tarde.'
});

// Aplicar o rate limit a todas as rotas
app.use(limiter);

// Rotas e configurações adicionais da sua aplicação...

// Iniciando o servidor na porta 3000
app.listen(3000, () => {
 console.log('Servidor rodando em http://localhost:3000');
});

Conclusão

Por fim, não deixe de treinar sua equipe e sempre seguir as melhores práticas do mercado (incluíndo as definições do OSWAP).

Lembre-se: A segurança das suas aplicações front-end são de responsabilidade compartilhada, ou seja, é crucial que você colabore estreitamente com a sua equipe de back-end para garantir que as melhores práticas de segurança estejam sendo atendidas.

Dessa forma você garante a segurança da aplicação e evita futuras dores de cabeça 🙂