Rotas protegidas em React: saiba tudo sobre React Router e JWT

Rocketseat

Rocketseat

7 min de leitura
react
E aí, dev! Bora codar? 👋
Se você está construindo uma aplicação web com React, cedo ou tarde vai se deparar com uma necessidade muito comum: garantir que apenas pessoas autorizadas acessem certas páginas. Imagine um site com áreas exclusivas - como um painel administrativo ou perfil do usuário - que só devem ser acessíveis após login. Essas são as chamadas rotas protegidas: páginas que verificam a autenticação antes de permitir o acesso.
Muitas pessoas recorrem a tutoriais antigos ou gambiarras complicadas para resolver isso, mas hoje a história é diferente. Vamos mostrar, passo a passo, como implementar rotas protegidas em React usando as versões mais recentes do ecossistema: React Router DOM v7 e a Context API do React para gerenciar autenticação via JSON Web Token (JWT). No final, você terá um fluxo completo de login, armazenamento do token e redirecionamento automático que serve de base para qualquer projeto profissional.

O que são rotas protegidas e por que você precisa delas?

Antes de botar a mão no código, vamos entender o conceito. Imagine que sua aplicação é como um aeroporto. Qualquer pessoa pode acessar o saguão principal. Mas para chegar ao portão de embarque, você precisa apresentar o cartão de embarque e passar pela segurança.
As rotas protegidas funcionam como essas áreas restritas do aeroporto: apenas quem comprovou sua identidade pode passar. No aeroporto, você apresenta seu cartão de embarque para acessar o portão. Na aplicação, você apresenta um token de autenticação.
Implementar esse tipo de rota traz diversos benefícios:
  • Segurança: informações sensíveis ficam protegidas, e ações críticas só podem ser executadas por quem tem permissão.
  • Controle de acesso: é possível definir diferentes níveis de acesso. Por exemplo, um administrador vê mais opções que um usuário comum.
  • Experiência do usuário: você guia cada pessoa de forma lógica, mostrando apenas o que faz sentido para o status dela (logada ou não), evitando confusão.
Em resumo, rotas protegidas aumentam a segurança da aplicação e melhoram a experiência, garantindo que cada usuário veja somente o que deve ver.

O que vamos construir?

Vamos praticar construindo uma aplicação React simples, com todos os conceitos necessários:
  • Página de login: pública, acessível a qualquer visitante.
  • Página de dashboard: privada, acessível apenas após login (uma rota protegida).
O fluxo será o seguinte: ao tentar acessar o dashboard sem estar autenticado, o usuário será redirecionado automaticamente para a tela de login. Após inserir as credenciais e fazer login (usaremos um JWT fake para simular a resposta de uma API), a pessoa terá acesso ao dashboard. Focaremos 100% na lógica do front‑end, sem depender de um back‑end real.
Com o plano em mente, vamos preparar o ambiente e decolar! 🚀

Preparando o ambiente

Antes de criar as rotas protegidas em React, precisamos configurar nosso projeto e instalar as dependências. Pense nisso como construir a base de lançamento do nosso foguete.

Iniciando o projeto React com Vite

Vamos criar um novo projeto React usando o Vite, que nos dá um pontapé inicial rápido. No terminal, execute:
npm create vite@latest rotas-protegidas-react -- --template react cd rotas-protegidas-react

Instalando as dependências: React Router DOM v7 e Axios

Com o projeto criado, precisamos instalar as bibliotecas essenciais:
npm install react-router-dom@^7 axios
Esse comando instala o React Router DOM v7 (a versão mais recente no momento) e o Axios. O React Router DOM permite definir rotas e navegar entre páginas; o Axios servirá para lidar com autenticação e requisições HTTP (simulando uma chamada de login e configurando o token nas requisições).

Estrutura de pastas recomendada

Organização é tudo! Dentro da pasta src/, crie as seguintes pastas:
src/ ├── contexts/ # Contexto de autenticação (AuthContext) ├── pages/ # Páginas da aplicação (LoginPage, DashboardPage) ├── routes/ # Configuração das rotas (PrivateRoute e AppRoutes) └── services/ # Serviços de API e autenticação (authService, api)
Remova arquivos desnecessários gerados pelo Vite (como App.css) para começar com uma estrutura mais limpa. Agora, vamos construir a lógica de autenticação.

A lógica da autenticação: nosso cofre de segurança com JWT

Antes de proteger as rotas, precisamos de uma forma de saber se o usuário está autenticado e de armazenar essa informação de forma segura. Aqui entra o JWT (JSON Web Token) — nosso crachá digital — e algumas estratégias no front‑end para guardá‑lo.

O que é JWT (JSON Web Token)?

O JWT é um crachá digital emitido pelo servidor quando o login é bem‑sucedido. Ele é uma string longa com informações codificadas que confirmam a identidade e permissões do usuário. No front‑end, guardamos esse token (geralmente no localStorage). A cada tentativa de acesso a uma área restrita ou requisição a uma API protegida, nosso app apresenta esse crachá. Se o crachá for válido, acesso liberado; se não, acesso negado.

Criando o serviço de autenticação (authService.js)

Para gerenciar o token JWT no front‑end, vamos criar um serviço que será responsável por salvar o token no login, removê‑lo no logout e fornecer funções utilitárias para verificar a autenticação.
Crie o arquivo src/services/authService.js e adicione:
// src/services/authService.js export const TOKEN_KEY = "@rocketseat-token"; // Verifica se já tem um token no localStorage (usuário logado) export const isAuthenticated = () => localStorage.getItem(TOKEN_KEY) !== null; // Pega o token do localStorage export const getToken = () => localStorage.getItem(TOKEN_KEY); // Salva o token (login) export const login = (token) => { localStorage.setItem(TOKEN_KEY, token); }; // Remove o token (logout) export const logout = () => { localStorage.removeItem(TOKEN_KEY); };
Explicando rapidamente:
  • TOKEN_KEY é a chave usada para salvar o JWT no localStorage.
  • isAuthenticated() retorna true ou false dependendo se há token salvo — verifica se o usuário está logado.
  • getToken() retorna o token (ou null se não houver).
  • login(token) salva o token no armazenamento local.
  • logout() remove o token e, assim, “desloga” o usuário.
Com esse serviço, controlamos a existência do JWT no navegador. Agora vamos garantir que, ao fazer requisições, o token seja enviado automaticamente.

Configurando o Axios com interceptors (api.js)

Uma dica de ouro: configure um interceptor do Axios para adicionar automaticamente o token JWT ao cabeçalho de todas as requisições. Assim você não precisa se lembrar de passar o token manualmente.
Crie src/services/api.js com o seguinte conteúdo:
// src/services/api.js import axios from "axios"; import { getToken } from "./authService"; const api = axios.create({ baseURL: "https://sua-api.com/api", // ajuste para a sua API real }); // Interceptor de requisições: adiciona o token JWT ao header Authorization, se existir api.interceptors.request.use((config) => { const token = getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); export default api;
Dessa forma, sempre que houver um token salvo, ele será anexado ao cabeçalho Authorization automaticamente. Caso não haja token, a requisição segue sem esse header.

Gerenciando o estado de autenticação com context API

Para que a aplicação inteira saiba se o usuário está logado ou não (e para compartilhar as funções de login e logout), vamos utilizar a Context API do React. Com ela, provemos o estado de autenticação para qualquer componente sem precisar passar props manualmente.

Criando o nosso AuthContext

Crie src/contexts/AuthContext.jsx com o seguinte código:
// src/contexts/AuthContext.jsx import React, { createContext, useState, useEffect, useContext } from "react"; import { TOKEN_KEY } from "../services/authService"; const AuthContext = createContext(null); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Verifica se há token no localStorage quando a aplicação carrega useEffect(() => { const token = localStorage.getItem(TOKEN_KEY); if (token) { // Aqui você poderia validar o token com a API e obter os dados reais do usuário setUser({ name: "Diego" }); // usuário mockado para exemplo } setLoading(false); }, []); // Função de login const login = async (email, password) => { // Em produção: faça uma chamada à API de login e receba { token, user } const token = "jwt-token-de-exemplo"; const loggedUser = { name: "Diego", email }; localStorage.setItem(TOKEN_KEY, token); setUser(loggedUser); }; // Função de logout const logout = () => { localStorage.removeItem(TOKEN_KEY); setUser(null); }; return ( <AuthContext.Provider value={{ isAuthenticated: !!user, user, loading, login, logout }} > {children} </AuthContext.Provider> ); }; // Hook para acessar facilmente o contexto export const useAuth = () => useContext(AuthContext);
Resumo dos pontos principais:
  • AuthContext armazena informações de autenticação (usuário, loading) e expõe funções de login e logout.
  • AuthProvider envolve nossa aplicação, verificando inicialmente se existe um token no localStorage. Se existir, define um usuário mockado (em um projeto real, você validaria o token com a API e buscaria os dados do usuário). Após essa verificação, loading é definido como false.
  • login(email, password) simula a chamada de API, salva o token e define os dados do usuário.
  • logout() remove o token e reseta o usuário.
  • Através do hook useAuth(), qualquer componente pode acessar isAuthenticated, user, loading, login e logout.
Com o contexto pronto, nossa aplicação saberá a todo momento se o usuário está autenticado. Agora vamos usar essa informação para proteger as rotas.

Mão na massa: construindo as rotas com React Router DOM

Chegou a hora de implementar as rotas em si. Com o React Router DOM v7, podemos continuar usando o “modo de biblioteca” familiar (com BrowserRouter, Routes, Route, Outlet e Navigate) sem mudanças drásticas, pois a atualização da v6 para a v7 foi pensada para não quebrar. Vamos criar um componente de rota privada que verifica a autenticação antes de renderizar a página desejada.

Criando o componente de Rota Privada (PrivateRoute.jsx)

Esse componente é nossa porta de segurança: checa se o usuário está autenticado; caso não esteja, redireciona para /login. Caso esteja, renderiza o componente filho correspondente.
Crie src/routes/PrivateRoute.jsx:
// src/routes/PrivateRoute.jsx import React from "react"; import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../contexts/AuthContext"; const PrivateRoute = () => { const { isAuthenticated, loading } = useAuth(); if (loading) { // Podemos retornar um spinner ou texto de carregando enquanto verifica o auth return <div>Carregando…</div>; } // Se estiver autenticado, renderiza o componente filho (Outlet); // Caso contrário, redireciona para login. return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />; }; export default PrivateRoute;
Como funciona:
  • Obtemos isAuthenticated e loading via useAuth().
  • Enquanto loading for true (a aplicação está verificando se existe token), exibimos algum indicador de carregamento.
  • Depois, se o usuário estiver autenticado, renderizamos o <Outlet />, que representa a rota filha protegida; caso contrário, usamos <Navigate /> para redirecionar imediatamente para /login.

Estruturando o arquivo de rotas (AppRoutes.jsx)

Agora definimos quais rotas são públicas e quais são privadas. Crie src/routes/AppRoutes.jsx:
// src/routes/AppRoutes.jsx import React from "react"; import { Routes, Route } from "react-router-dom"; import LoginPage from "../pages/LoginPage"; import DashboardPage from "../pages/DashboardPage"; import PrivateRoute from "./PrivateRoute"; const AppRoutes = () => { return ( <Routes> {/* Rota pública */} <Route path="/login" element={<LoginPage />} /> {/* Wrapper de rotas privadas */} <Route path="/" element={<PrivateRoute />}> {/* Qualquer rota declarada aqui dentro só será acessível se o usuário estiver autenticado */} <Route path="/dashboard" element={<DashboardPage />} /> {/* Adicione mais rotas privadas aqui, se precisar */} </Route> {/* 404 */} <Route path="*" element={<div>Página não encontrada!</div>} /> </Routes> ); }; export default AppRoutes;
Observe que PrivateRoute envolve as rotas protegidas com <Outlet />. Qualquer rota aninhada dentro desse wrapper só será acessível quando isAuthenticated for true. Caso contrário, a pessoa será redirecionada para /login. A rota /login fica fora do wrapper porque é pública.

Extra (opcional): Data Router v7 com loaders e redirect

O React Router DOM também oferece um “modo framework”, inspirado no Remix, que usa loaders e actions para carregar dados e redirecionar no nível da rota. É uma abordagem alternativa ao componente guard. Um exemplo rápido:
// src/routes/router.jsx (opcional) import { createBrowserRouter, RouterProvider, redirect } from "react-router-dom"; import LoginPage from "../pages/LoginPage"; import DashboardPage from "../pages/DashboardPage"; import { getToken } from "../services/authService"; // Protege a rota usando loader async function requireAuth() { const token = getToken(); if (!token) throw redirect("/login"); return null; } const router = createBrowserRouter([ { path: "/login", element: <LoginPage /> }, { path: "/", children: [ { path: "/dashboard", element: <DashboardPage />, loader: requireAuth }, ], }, ]); export const AppRouter = () => <RouterProvider router={router} />;
Aqui, a função requireAuth é um loader que lança redirect('/login') se não houver token. Essa abordagem remove a necessidade de um componente guard. Para usar essa versão, basta renderizar <AppRouter /> em vez de <AppRoutes /> no App.jsx.

Construindo as páginas: a interface da nossa aplicação

Com a lógica de autenticação pronta, criar as páginas é simples. Teremos duas páginas principais: Login e Dashboard.

Página de login (LoginPage.jsx)

Crie src/pages/LoginPage.jsx:
// src/pages/LoginPage.jsx import React, { useState } from "react"; import { useAuth } from "../contexts/AuthContext"; import { useNavigate } from "react-router-dom"; const LoginPage = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const { login } = useAuth(); const navigate = useNavigate(); const handleSubmit = async (e) => { e.preventDefault(); try { await login(email, password); // autenticação JWT navigate("/dashboard"); // redireciona após login } catch (err) { console.error("Falha no login", err); alert("Credenciais inválidas. Tente novamente!"); } }; return ( <div> <h1>Login</h1> <form onSubmit={handleSubmit}> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} required /> <input type="password" placeholder="Senha" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit">Entrar</button> </form> </div> ); }; export default LoginPage;
Fluxo:
  1. Usa useState para controlar os campos de email e senha.
  1. Usa useAuth() para pegar a função login do contexto.
  1. Usa useNavigate() para redirecionar após o login.
  1. No handleSubmit, impede o reload do formulário, chama login(email, password) e, em caso de sucesso, usa navigate('/dashboard') para levar a pessoa à rota protegida.

Página de dashboard (DashboardPage.jsx)

Crie src/pages/DashboardPage.jsx:
// src/pages/DashboardPage.jsx import React from "react"; import { useAuth } from "../contexts/AuthContext"; import { useNavigate } from "react-router-dom"; const DashboardPage = () => { const { user, logout } = useAuth(); const navigate = useNavigate(); const handleLogout = () => { logout(); navigate("/login"); }; return ( <div> <h1>Dashboard</h1> <p>Bem-vindo(a), {user?.name || "dev"}!</p> <button onClick={handleLogout}>Sair</button> </div> ); }; export default DashboardPage;
A página de dashboard dá boas-vindas ao usuário autenticado (usando user.name vindo do contexto) e oferece um botão Sair que remove o token e redireciona para a página de login.

Juntando todas as peças no App.jsx

Com rotas, contexto e páginas prontas, falta integrar tudo no nosso componente principal. Vamos envolver a aplicação com o BrowserRouter do React Router DOM e com o AuthProvider do contexto.
Crie ou ajuste o arquivo src/App.jsx:
// src/App.jsx import React from "react"; import { BrowserRouter } from "react-router-dom"; import { AuthProvider } from "./contexts/AuthContext"; import AppRoutes from "./routes/AppRoutes"; // Se estiver usando a abordagem Data Router, importe AppRouter em vez de AppRoutes function App() { return ( <BrowserRouter> <AuthProvider> <AppRoutes /> {/* ou <AppRouter /> se usar o Data Router */} </AuthProvider> </BrowserRouter> ); } export default App;
E ajuste o ponto de entrada principal src/main.jsx se necessário:
// src/main.jsx import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <App /> </React.StrictMode> );

Teste rápido (checklist)

  1. Rode npm run dev e abra o projeto (tipicamente em http://localhost:5173).
  1. Tente acessar http://localhost:5173/dashboard diretamente sem estar logado. Você deve ser redirecionado para /login.
  1. Faça login com qualquer email e senha (estamos simulando). Você deve ser redirecionado para o dashboard.
  1. Clique em Sair no dashboard para remover o token e voltar ao login.
Quando quiser usar uma API real, substitua a lógica de login por uma chamada real, valide o token no back‑end e, se quiser mais segurança, armazene o JWT em cookies httpOnly. Também é comum implementar refresh tokens para renovar a sessão.

Conclusão: suas rotas protegidas React estão prontas para decolar!

Ufa! Que jornada, hein? Passamos por toda a configuração do ambiente com Vite, entendemos a lógica de autenticação com JWT, implementamos um sistema global de gerenciamento de usuário com Context API e, finalmente, construímos rotas protegidas utilizando o que há de mais moderno no React Router DOM, cujas mudanças são compatíveis com a versão anterior.
O resultado é uma aplicação React com controle de acesso profissional, pronta para ser expandida. Agora você tem a base para qualquer app que precise de autenticação e níveis de permissão. A partir daqui dá para evoluir muito: criar diferentes níveis de acesso (usuário, admin, etc.), proteger rotas aninhadas, integrar com back‑ends reais, explorar o data router e aproveitar as novas features que aproximam o React Router do Remix.
Lembre‑se: o mais importante na jornada de uma pessoa desenvolvedora é praticar constantemente e manter a curiosidade em alta. Explore novas possibilidades, refatore seu código, teste diferentes abordagens e continue evoluindo. E claro, conte com a comunidade e com a Rocketseat nessa jornada para alcançar voos ainda mais altos.
Quer ir além de rotas protegidas e dominar todo o ecossistema React? Na Formação em React da Rocketseat você aprende na prática, do zero ao avançado, construindo projetos reais que vão direto para o seu portfólio. Você vai mergulhar nas tecnologias mais usadas no mercado, construir interfaces reativas do jeito certo e acelerar sua carreira front‑end.
 
Artigos_

Explore conteúdos relacionados

Descubra mais artigos que complementam seu aprendizado e expandem seu conhecimento.