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

Rocketseat

Navegação Rápida:
O que são rotas protegidas e por que você precisa delas?
1O que vamos construir?
2Preparando o ambiente
3A lógica da autenticação: nosso cofre de segurança com JWT
4Gerenciando o estado de autenticação com context API
5Mão na massa: construindo as rotas com React Router DOM
6Construindo as páginas: a interface da nossa aplicação
7Juntando todas as peças no
8Teste rápido (checklist)
9Conclusão: suas rotas protegidas React estão prontas para decolar!
10
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 nolocalStorage
.
isAuthenticated()
retornatrue
oufalse
dependendo se há token salvo — verifica se o usuário está logado.
getToken()
retorna o token (ounull
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 delogin
elogout
.
AuthProvider
envolve nossa aplicação, verificando inicialmente se existe um token nolocalStorage
. 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 comofalse
.
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 acessarisAuthenticated
,user
,loading
,login
elogout
.
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
eloading
viauseAuth()
.
- Enquanto
loading
fortrue
(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:
- Usa
useState
para controlar os campos de email e senha.
- Usa
useAuth()
para pegar a funçãologin
do contexto.
- Usa
useNavigate()
para redirecionar após o login.
- No
handleSubmit
, impede o reload do formulário, chamalogin(email, password)
e, em caso de sucesso, usanavigate('/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)
- Rode
npm run dev
e abra o projeto (tipicamente emhttp://localhost:5173
).
- Tente acessar
http://localhost:5173/dashboard
diretamente sem estar logado. Você deve ser redirecionado para/login
.
- Faça login com qualquer email e senha (estamos simulando). Você deve ser redirecionado para o dashboard.
- 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.