Crie Autenticação Segura com JWT

Aprenda a implementar autenticação segura com JWT (JSON Web Token) em aplicações Full Stack com React e Node.js!

Autenticação segura com JWT (JSON Web Token) funciona assim: no login, o backend valida as credenciais e devolve um token assinado; o frontend envia esse token no header Authorization a cada requisição, e um middleware verifica a assinatura antes de liberar as rotas protegidas — sem guardar sessão no servidor.

Introdução

Fala, impressionador!

Se você está desenvolvendo aplicações web, uma das coisas mais importantes que você precisa aprender é como criar um sistema de autenticação seguro. Não adianta nada ter uma aplicação incrível se qualquer pessoa consegue acessar rotas que deveriam ser privadas, certo?

Neste artigo, você vai aprender a implementar autenticação JWT (JSON Web Token) do zero, conectando front-end e back-end de forma segura. Vamos partir de um projeto básico de React com Node.js e adicionar toda a camada de segurança que um sistema profissional precisa.

O melhor de tudo? Vamos fazer isso passo a passo, de forma didática e prática. Ao final, você terá um sistema completo de login onde:

  • O usuário faz login e recebe um token de autenticação
  • Esse token é armazenado no navegador
  • Todas as rotas protegidas verificam se o token é válido
  • Se o usuário atualizar a página, ele continua logado
  • Se tentar acessar uma rota sem estar autenticado, é redirecionado para o login

Isso é exatamente o que grandes aplicações como Netflix, Instagram e qualquer sistema profissional fazem. E você vai aprender a fazer também!

Para fazer o download do(s) arquivo(s) utilizados na aula, preencha com o seu e-mail:

Não vamos te encaminhar nenhum tipo de SPAM! A Hashtag Treinamentos é uma empresa preocupada com a proteção de seus dados e realiza o tratamento de acordo com a Lei Geral de Proteção de Dados (Lei n. 13.709/18). Qualquer dúvida, nos contate.

Preparado? Então vamos começar!

Preparando o ambiente

Para o exemplo deste artigo, você pode baixar o projeto do zero no material disponível da aula. Após baixar o projeto é necessário:

  1. Selecione a pasta "Do Zero", clique no ícone de três pontinhos e clique na opção Baixar;
Captura de tela do Google Drive mostrando a pasta 'Do Zero' selecionada. Ao clicar no botão de opções representado por três pontos, aparece um menu suspenso com várias ações, destacando a opção 'Baixar' no topo
  1. Descompactar a pasta. Para isso, clique com o botão direito do mouse sobre a pasta e selecione a opção Extrair tudo… em seguida clique no botão Extrair;
Captura de tela do Windows Explorer mostrando o menu de contexto aberto após selecionar um arquivo compactado. No menu aparecem opções como Abrir, Abrir com, Copiar, Renomear e Compartilhar. A opção 'Extrair Tudo...' está destacada na parte inferior do menu
  1. Abrir a pasta no editor de código, por exemplo o VS Code;
  2. No interior da pasta baixada, teremos duas pastas principais: "back-end" e "front-end". Clique com o botão direito do mouse sobre cada uma delas e selecione a opção Open in integrated Terminal;
Captura de tela do Visual Studio Code mostrando o menu de contexto aberto sobre uma pasta chamada 'back-end' dentro do projeto 'DO ZERO'. No menu aparecem opções como New File, New Folder e Reveal in File Explorer. A opção 'Open in Integrated Terminal' está destacada em vermelho
  1. Você precisará rodar o comando npm install no terminal de cada pasta para instalar as dependências do projeto;
  2. No terminal da pasta "back-end", use o comando npm start para rodar o back-end da aplicação;
  3. Em sequência, no terminal da pasta "front-end", use o comando npm run dev para inicializar o projeto;
  4. Acesse a aplicação com o link: "http://localhost:5173/login".

Pronto, o projeto inicial já está rodando e podemos seguir com a autenticação!

O que é JWT (JSON Web Token)?

JWT significa JSON Web Token, que é um padrão aberto (RFC 7519) para transmitir informações de forma segura entre cliente e servidor. Em outras palavras, o JWT é um token que contém dados codificados e uma assinatura que garante que esses dados não foram alterados.

Quando você faz login em um site, ao invés de o servidor precisar verificar sua senha toda hora, ele cria um token JWT que  garante que o usuário(a) está autenticado e envia para você. Com isso, toda vez que você faz uma requisição, você manda esse token junto, e o servidor verifica se ele é válido.

Pense no JWT como se fosse uma pulseira de identificação em um festival: você mostra ela na entrada e consegue acessar o evento sem precisar comprar ingresso toda hora.

Estrutura de um token JWT

Um token JWT é dividido em três partes separadas por pontos:

  • Header (cabeçalho): contém o tipo do token (JWT) e o algoritmo de criptografia usado;
  • Payload (dados): contém as informações do usuário, como e-mail, ID e tempo de expiração;
  • Signature (assinatura): garante que o token não foi modificado, usando uma chave secreta.

Então quando você recebe um token como: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFiY0BleGFtcGxlLmNvbSJ9.signature, essas três partes garantem a segurança da sua autenticação.

Por que usar JWT para autenticação?

Existem várias formas de fazer autenticação em aplicações web, mas o JWT tem se tornado cada vez mais popular por alguns motivos importantes:

  • Stateless: o servidor não precisa guardar sessões, tudo está no token;
  • Escalabilidade: funciona bem em sistemas distribuídos com múltiplos servidores;
  • Segurança: a assinatura garante que o token não foi adulterado;
  • Flexibilidade: você pode definir tempo de expiração e dados customizados;
  • Padrão da indústria: amplamente usado e com bibliotecas para todas as linguagens.

Como funciona a autenticação com JWT?

O fluxo de autenticação com JWT funciona assim:

  1. Login: o usuário envia e-mail e senha para o servidor
  2. Verificação: o servidor verifica se os dados estão corretos
  3. Geração do token: se estiver certo, o servidor cria um JWT e envia para o cliente
  4. Armazenamento: o cliente guarda o token (geralmente no localStorage)
  5. Requisições: em toda requisição, o cliente envia o token no cabeçalho
  6. Validação: o servidor verifica se o token é válido antes de processar a requisição
  7. Acesso permitido: se o token for válido, o servidor processa a requisição normalmente

Esse processo garante que apenas usuários autenticados consigam acessar rotas protegidas da aplicação.

Criando o token de acesso

Dentro da pasta "back-end", no arquivo "server.js", na linha 19, há uma condição simples que verifica se o e-mail e a senha são iguais aos dados enviados pelo usuário(a) ao preencher o formulário. Caso sejam, o site é redirecionado para a página de saudação.

SCSS
if (email === USUARIO && password === SENHA) {
    res.redirect("http://localhost:5173/saudacao");
  }

É neste trecho que queremos criar a autenticação! Para isso precisamos instalar o JWT e isso pode ser feito da seguinte maneira:

  1. Clique com o botão direito do mouse sobre a pasta "back-end" e selecione a opção Open in Integrated Terminal;
  2. No novo terminal criado para o back-end, digite o comando: npm install jsonwebtoken e aguarde a instalação;

Em seguida, precisamos importar a biblioteca para dentro do arquivo "server.js". Por isso, ao final da linha três clique no botão Enter e, na linha 4 use o código: import jwt from "jsonwebtoken".

O próximo passo é criar o token de acesso toda vez que meu usuário(a) acessar o site. Para isso, dentro da condição if (email === USUARIO && password === SENHA) na linha 19, precisamos criar uma constante que vai receber como parâmetros:

  • payload: dados de acesso do usuário(a), neste caso, "email" e "password";
  • SecretKey: que podemos criar no arquivo ".env";
  • informações extras: outros parâmetros opcionais como, tempo de expiração do token.

O código ficará assim:

Codigo
if (email === USUARIO && password === SENHA) {

    const accessToken = jwt.sign({ email, password }, ACCESS_TOKEN_KEY, {

      expiresIn: "15m",

    });

    res.json({ accessToken });

Lembre-se de adicionar no arquivo ".env" a ACCESS_TOKEN_KEY com um valor aleatório:

Codigo
ACESS_TOKEN_KEY=[escreva um valor aleatório]

Além disso, para que a chave de acesso seja processada é necessário adicioná-la à constante que contém as variáveis de ambiente na linha 10.

Codigo
const { USUARIO, SENHA, ACCESS_TOKEN_KEY } = process.env;
Ícone Full StackFull Stack Impressionador

Essa é uma trilha de especialização que vai te tornar um verdadeiro Desenvolvedor Web Full Stack, dominando todas as principais tecnologias usadas tanto para o Front End quanto para o Backend e ainda para Mobile, construindo projetos práticos e exercícios aplicados à realidade das empresas. Tudo o que você precisa para se tornar um Desenvolvedor Full Stack em um único lugar.

Começar agoraSeta para a direita
Fundo Full StackTelas Full Stack
Luz Full Stack

Enviando as informações pelo formulário

Na pasta "front-end" no arquivo "Login.jsx" dentro de "pages", atualmente estamos enviando diretamente as informações do formulário através do método POST sem autenticação. Contudo, vamos realizar algumas mudanças na maneira como as informações são enviadas para acessar a tela de saudação.

O primeiro passo é instalar a biblioteca Axios que usaremos para realizar a requisição do tipo POST. Caso queira explorar mais sobre essa biblioteca, recomendo o vídeo AXIOS vs FETCH: Qual a melhor forma de consumir APIs.

O processo de instalação é o seguinte:

  1. Clique com o botão direito do mouse sobre a pasta "front-end" e selecione a opção Open in Integrated Terminal;
  2. No novo terminal criado para o back-end, digite o comando: npm install axios e aguarde a instalação;

Em seguida, vamos importar as bibliotecas que usaremos:

Python
import axios from "axios";

//Função React para manter e atualizar o estado local do componente
import { useState } from "react";

 //Função para navegar entre rotas
import { useNavigate } from "react-router-dom";

Na sequência, é necessário criar dois estados locais, um para email e outro para password, inicializados como strings vazias para armazenar os valores digitados nos inputs.

Codigo
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

Também precisamos inicializar a função navigate, que permite redirecionar o usuário para outra rota, pois após receber a resposta bem-sucedida do servidor, o aplicativo deve redirecionar o usuário para a tela de saudação.

Codigo
const navigate = useNavigate();

O próximo passo é criar a função handleSubmit que vai fazer o envio do formulário e redirecionamento da rota:

Codigo
const handleSubmit = async (e) => {
  // Impede o comportamento padrão do formulário (recarregar a página)
  e.preventDefault();

  try {
    // Envia uma requisição POST para o backend com email e senha
    // O 'await' faz o código esperar a resposta antes de continuar
    const { data } = await axios.post("http://localhost:3001/api/login", {
      email,      // valor digitado no input de email
      password,   // valor digitado no input de senha
    });

    // Armazena o token retornado pelo servidor no localStorage
    // Isso permite manter o usuário logado entre páginas/reloads
    localStorage.setItem("token", data.accessToken);

    // Redireciona o usuário para a rota de saudacao após login bem-sucedido
    navigate("/saudacao");
  } catch (error) {

    // Exibe um alerta caso o login falhe (email ou senha errados ou erro no servidor)
    alert("Email ou senha inválidos!");

    // Mostra detalhes técnicos do erro no console para ajudar no debug
    console.error(`Deu algum erro: ${error}`);
  }
};

Agora com a função estruturada, precisamos inserí-la no formulário e capturar os valores adicionados em cada campo.

HTML
<form onSubmit={handleSubmit}>
  <h1>Faça seu login</h1>
  <input type="email" name="email" id="email" placeholder="Digite seu e-mail"
         required onChange={(e) => setEmail(e.target.value)} />
  <input type="password" name="password" id="password" placeholder="Digite sua senha"
         required onChange={(e) => setPassword(e.target.value)} />
  <button>Fazer login</button>
</form>

O <form> chama a função handleSubmit ao submeter (onSubmit) e cada <input> tem:

  • type apropriado (email / password) para validação nativa e teclado correto em mobile;
  • required exige preenchimento (validação do navegador);
  • onChange atualiza o estado email/password conforme o usuário digita.

O <button> envia o formulário (tipo padrão submit).

Verificando se o usuário(a) está autenticado na tela de saudação

Para validar se o usuário está realmente autenticado ao acessar a tela de saudação, precisamos confirmar se o token salvo no navegador ainda é válido. Como o token já foi gerado e armazenado no localStorage durante o login, o próximo passo é criar uma rota protegida no backend e fazer a verificação no frontend.

Dentro do arquivo server.js, na pasta back-end, criamos uma rota do tipo GET chamada /api/protected. Essa rota serve exclusivamente para verificar se o token enviado pelo cliente é válido. O código fica assim:

Codigo
app.get("/api/protected", (req, res) => {
  // Pega o header Authorization enviado pelo cliente
  const authHeader = req.headers["authorization"];
  // Extrai apenas o token (formato esperado: "Bearer <token>")
  const token = authHeader ? authHeader.split(" ")[1] : undefined;
  // Se não houver token, retorna erro 401 (não autorizado)
  if (!token) res.status(401);
  // Verifica se o token é válido usando a chave ACCESS_TOKEN_KEY
  jwt.verify(token, ACCESS_TOKEN_KEY, (err, user) => {
    // Se o token for inválido ou expirado, retorna erro 403 (proibido)
    if (err) {
      res.status(403);
      return;
    }
    // Se estiver tudo certo, responde com mensagem e os dados do usuário contidos no token
    res.json({ message: "Usuário autenticado", user });
  });
});

Essa rota realiza três verificações importantes:

  1. Confere se o token foi enviado no header Authorization;
  2. Valida o token usando a chave que foi usada para assiná-lo no login;
  3. Permite ou bloqueia o acesso, dependendo da validade do token.

Se o token estiver correto, a rota devolve os dados do usuário. Caso contrário, retorna erro 401 ou 403, indicando que o usuário deve fazer login novamente.

Agora, no arquivo "Saudacao.jsx", criamos um useEffect que roda automaticamente quando a página é carregada. Esse código verifica se o token ainda é válido antes de permitir que o usuário permaneça na tela:

Codigo
useEffect(() => {
  // Função que verifica se o usuário está autenticado
  const checarAutenticacao = async () => {
    // Pega o token salvo no navegador
    const token = localStorage.getItem("token");
    // Se não houver token, redireciona para o login
    if (!token) navigate("/login");
    try {
      // Faz uma requisição GET para a rota protegida
      // Envia o token no header Authorization
      const response = await axios.get(
        "http://localhost:3001/api/protected",
        {
          headers: { Authorization: `Bearer ${token}` },
        }
      );
      // Se o backend retornar 401 ou 403, significa que o token é inválido
      if (response.status === "401" || response.status === "403")
        navigate("/login");
    } catch (error) {
      // Mostra o erro no console para ajudar a debugar
      console.error(`Deu algum erro: ${error}`);
    }
  };
  // Executa a verificação assim que o componente for montado
  checarAutenticacao();
}, []);

Esse trecho realiza uma série de verificações automáticas:

  • Busca o token no localStorage;
  • Caso não encontre, leva o usuário diretamente para o login;
  • Envia o token para o backend para validar se ele ainda é válido;
  • Caso o backend retorne erro (token inválido ou expirado), o usuário também é redirecionado;
  • Isso impede que alguém acesse a tela da saudação simplesmente colando a URL no navegador.

Lembre-se de fazer as importações necessárias e criar a constante navigate para fazer a navegação para a página de login, caso o token esteja expirado.

Python
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
Codigo
const navigate = useNavigate();

Implementando autenticação na tela Home

Caso tentemos acessar a página "http://localhost:5173/" não vamos conseguir e vamos cair em uma página em branco, pois no nosso projeto não temos essa rota definida.

O objetivo é criar essa página dentro da pasta "front-end/pages" para que ela redirecione a pessoa para a tela de login caso o(a) usuário(a) não esteja autenticado ou para a tela de saudação caso o(a) usuário(a) esteja autenticado. Para isso, podemos:

  1. Criar o arquivo "Home.jsx" dentro da pasta "front-end/pages";
  2. Utilizar o seguinte código:
HTML
import axios from "axios";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

const Home = () => {
  const navigate = useNavigate(); // Hook para redirecionar o usuário

  useEffect(() => {
    // Função que valida se o usuário está autenticado
    const checarAutenticacao = async () => {
      const token = localStorage.getItem("token"); // Busca o token salvo

      if (!token) navigate("/login"); // Se não houver token, vai para o login

      try {
        // Envia o token para o backend para validar
        const response = await axios.get(
          "http://localhost:3001/api/protected",
          {
            headers: { Authorization: `Bearer ${token}` }, // Token no header
          }
        );

        // Se o token for inválido, redireciona para o login
        if (response.status === "401" || response.status === "403") {
          navigate("/login");
        } else {
          navigate("/saudacao"); // Se estiver tudo certo, vai para a saudação
        }
      } catch (error) {
        console.error(`Deu algum erro: ${error}`); // Loga erros inesperados
      }
    };

    checarAutenticacao(); // Executa a verificação ao montar o componente
  }, []);

  return <div>Home</div>; // Conteúdo simples da tela inicial
};

export default Home;

Para que essa rota exista, precisamos abrir o arquivo "App.jsx" que está na pasta "front-end/src" e dentro da tag <Routes> criar a rota: <Route path="/" element={<Home />} />. Lembre-se de importar o arquivo "Home.jsx" no topo da página: import Home from "../pages/Home";

Pronto! Com isso, sempre que você acessar a página principal da aplicação através da URL "http://localhost:5173/", caso esteja autenticado e o token de acesso ainda esteja válido você será redirecionado para a tela de "Saudação', contudo, se o token estiver expirado ou você não tiver feito login, será redirecionado para a tela de "Login".

Perguntas frequentes

1. O que é JWT?

JSON Web Token é um padrão de token composto por três partes — header, payload e assinatura — codificadas em Base64. O payload carrega dados como id e permissões do usuário; a assinatura, gerada com uma chave secreta, garante que o conteúdo não foi adulterado.

2. Onde guardar o token JWT no frontend?

As duas opções têm trade-offs: cookie httpOnly protege contra roubo por XSS (o JavaScript não lê o token), enquanto localStorage é mais simples, porém exposto a scripts. Em produção, a combinação comum é cookie httpOnly + proteção CSRF e tokens de vida curta.

3. O que faz o middleware de autenticação?

Ele intercepta cada requisição às rotas protegidas: extrai o token do header, verifica assinatura e validade com a chave secreta e, se estiver tudo certo, anexa os dados do usuário à requisição e libera o fluxo. Token ausente ou inválido recebe resposta 401.

4. O JWT expira? Como renovar sem deslogar o usuário?

Sim — o claim exp define a validade, e tokens curtos (minutos a poucas horas) limitam o estrago de um vazamento. A renovação usa o refresh token: um token de vida longa, guardado com mais proteção, que emite novos access tokens sem pedir login de novo.

Conclusão

Neste artigo, aprendemos a criar tokens de acesso, verificá-los no servidor e proteger rotas tanto no back-end quanto no front-end. Com isso, você já possui os conhecimentos necessários para implementar sistemas de login seguros em seus próprios projetos.

Caso você queira aprender mais sobre desenvolvimento Full Stack, incluindo integração com bancos de dados, deploy e boas práticas de segurança, conheça nosso curso Fullstack Impressionador. Além disso, se preferir esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

Além disso, caso prefira esse conteúdo no formato de vídeo-aula, pode acessar o vídeo no nosso canal do Youtube.

O que você aprende neste vídeo

Resposta rápida: A aula adiciona autenticação com JWT a um projeto React e Node já conectado, do login até as rotas protegidas. Você aprende a assinar o token com jwt.sign(), guardar a chave em variável de ambiente, expirar a sessão, salvar o token no localStorage, enviá-lo no header Authorization e validá-lo com jwt.verify() em cada acesso.

Neste vídeo (36 min):

  • 1:30 - o problema: a tela de saudação abre sem login e a sessão se perde a cada F5
  • 6:30 - criar o token no login com jwt.sign(), passando o objeto do usuário como payload
  • 8:05 - guardar a chave secreta em variável de ambiente e definir a validade com expiresIn
  • 9:05 - devolver o token com res.json() e tratar o envio do formulário num handleSubmit
  • 10:30 - trocar o POST nativo do formulário pelo Axios e guardar os campos com useState
  • 14:00 - chamada com async/await e try/catch, e e.preventDefault() para a página não recarregar
  • 16:55 - salvar o token no localStorage e ir para a tela de saudação com useNavigate
  • 19:35 - criar a rota GET /api/protected, que responde se o usuário continua autenticado
  • 20:55 - mandar o token no header Authorization com o prefixo Bearer em cada requisição
  • 23:05 - ler o header no servidor e responder 401 sem token e 403 quando jwt.verify() reprova
  • 27:05 - disparar a checagem uma vez só no useEffect, com a lista de dependências vazia
  • 31:50 - repetir a proteção na tela Home e testar o fluxo apagando o token pelo navegador

Trechos do vídeo:

  • O JWT dispensa guardar sessão no servidor: o token assinado viaja no header a cada requisição e o backend só confere a assinatura.
  • A chave que assina o token fica em variável de ambiente no servidor, e é a mesma usada depois para verificar se o token é válido.
  • O header chega no formato Bearer mais o token, separados por espaço: o servidor precisa dividir a string e usar a segunda parte.
  • Sem token o servidor responde 401, de não autorizado; com token inválido ou expirado, responde 403, de proibido.

Hashtag Treinamentos

Para acessar publicações de Full Stack, clique aqui!


Quer aprender mais sobre JavaScript com um minicurso básico gratuito?








Posts mais recentes de Full Stack

Posts mais recentes da Hashtag Treinamentos