← Regresar a lecciones

Rutas Privadas con React Router: Protegiendo Rutas de Aplicación

Entendiendo las Rutas Privadas
Implementando Rutas Privadas con React Router

En las aplicaciones web modernas, ciertas rutas o páginas solo deben ser accesibles para usuarios autenticados. React Router, combinado con el contexto de autenticación que construimos en la lección anterior, proporciona una base sólida para crear rutas privadas e implementar guardias de ruta.

Entendiendo las Rutas Privadas

Las rutas privadas son componentes que restringen el acceso a rutas específicas basándose en el estado de autenticación. Si un usuario no está autenticado, es redirigido a una página de inicio de sesión u otra ruta apropiada en lugar de acceder al contenido protegido.

El flujo básico de las rutas privadas es:

  1. Verificar si el usuario está autenticado
  2. Si está autenticado, renderizar la ruta solicitada
  3. Si no está autenticado, redirigir a la página de inicio de sesión

Implementando Rutas Privadas con React Router

Existen múltiples enfoques para implementar rutas privadas con React Router, dependiendo de la versión que estés utilizando. Exploremos los patrones más comunes, comenzando con el enfoque más reciente utilizando React Router v6:

Rutas Privadas en React Router v6

React Router v6 ha simplificado la forma en que podemos implementar rutas privadas. En lugar de crear un componente PrivateRoute dedicado, podemos aprovechar el nuevo sistema de enrutamiento para proteger rutas con mayor flexibilidad:

1import { BrowserRouter, Routes, Route, Navigate, Outlet } from "react-router-dom"; 2import { useAuth } from "./context/AuthContext"; 3import Dashboard from "./pages/Dashboard"; 4import Profile from "./pages/Profile"; 5import Login from "./pages/Login"; 6import Home from "./pages/Home"; 7 8// Componente de guardia de autenticación 9function RequireAuth({ children }) { 10 const { isAuthenticated, loading } = useAuth(); 11 12 // Mostrar estado de carga o spinner mientras se verifica la autenticación 13 if (loading) { 14 return <div>Cargando...</div>; 15 } 16 17 // Redirigir a login si no está autenticado 18 if (!isAuthenticated()) { 19 return <Navigate to="/login" />; 20 } 21 22 // Renderizar hijos si está autenticado 23 return children; 24} 25 26function App() { 27 return ( 28 <BrowserRouter> 29 <Routes> 30 <Route path="/" element={<Home />} /> 31 <Route path="/login" element={<Login />} /> 32 33 {/* Rutas protegidas */} 34 <Route path="/dashboard" element={ 35 <RequireAuth> 36 <Dashboard /> 37 </RequireAuth> 38 } /> 39 40 <Route path="/profile" element={ 41 <RequireAuth> 42 <Profile /> 43 </RequireAuth> 44 } /> 45 46 {/* Grupo de rutas protegidas usando Outlet */} 47 <Route path="/admin" element={ 48 <RequireAuth> 49 <Outlet /> 50 </RequireAuth> 51 }> 52 <Route index element={<AdminDashboard />} /> 53 <Route path="users" element={<AdminUsers />} /> 54 <Route path="settings" element={<AdminSettings />} /> 55 </Route> 56 57 {/* Ruta de respaldo */} 58 <Route path="*" element={<NotFound />} /> 59 </Routes> 60 </BrowserRouter> 61 ); 62}

El componente RequireAuth actúa como un envoltorio para las rutas que necesitan protección. Verifica si el usuario está autenticado y renderiza el componente protegido o redirige a la página de inicio de sesión.

React Router v6 también introduce Outlet, que te permite proteger grupos enteros de rutas con una sola verificación de autenticación, como se muestra en el ejemplo de rutas de administrador.

Rutas Privadas en React Router v5

Si estás utilizando React Router v5, el enfoque es ligeramente diferente. Crearás un componente PrivateRoute personalizado:

1import React from "react"; 2import { Route, Redirect } from "react-router-dom"; 3import { useAuth } from "./context/AuthContext"; 4 5// Componente de ruta privada para React Router v5 6function PrivateRoute({ component: Component, ...rest }) { 7 const { isAuthenticated, loading } = useAuth(); 8 9 return ( 10 <Route 11 {...rest} 12 render={props => { 13 if (loading) { 14 return <div>Cargando...</div>; 15 } 16 17 return isAuthenticated() ? ( 18 <Component {...props} /> 19 ) : ( 20 <Redirect 21 to={{ 22 pathname: "/login", 23 state: { from: props.location } 24 }} 25 /> 26 ); 27 }} 28 /> 29 ); 30}

Y usarlo en tu configuración de rutas:

1import { BrowserRouter, Switch, Route } from "react-router-dom"; 2import PrivateRoute from "./components/PrivateRoute"; 3import Dashboard from "./pages/Dashboard"; 4import Profile from "./pages/Profile"; 5import Login from "./pages/Login"; 6import Home from "./pages/Home"; 7 8function App() { 9 return ( 10 <BrowserRouter> 11 <Switch> 12 <Route exact path="/" component={Home} /> 13 <Route path="/login" component={Login} /> 14 15 <PrivateRoute path="/dashboard" component={Dashboard} /> 16 <PrivateRoute path="/profile" component={Profile} /> 17 18 <Route path="*" component={NotFound} /> 19 </Switch> 20 </BrowserRouter> 21 ); 22}

Mejorando la Experiencia de Usuario con Redirección Después del Login

Un patrón común de UX es redirigir a los usuarios de vuelta a la página que estaban tratando de acceder después de iniciar sesión. Implementemos esta característica utilizando React Router:

1import React, { useState } from "react"; 2import { useNavigate, useLocation } from "react-router-dom"; 3import { useAuth } from "../context/AuthContext"; 4 5function Login() { 6 const [email, setEmail] = useState(""); 7 const [password, setPassword] = useState(""); 8 const [error, setError] = useState(""); 9 10 const { login } = useAuth(); 11 const navigate = useNavigate(); 12 const location = useLocation(); 13 14 // Obtener la ruta de redirección del estado de location o por defecto dashboard 15 const from = location.state?.from?.pathname || "/dashboard"; 16 17 const handleSubmit = async (e) => { 18 e.preventDefault(); 19 setError(""); 20 21 try { 22 await login(email, password); 23 // Redirigir a la página que estaban intentando acceder 24 navigate(from, { replace: true }); 25 } catch (error) { 26 setError("Error al iniciar sesión"); 27 } 28 }; 29 30 // Resto del componente... 31}

Cuando se usa React Router v5, la implementación es similar pero usa diferentes hooks:

1// Versión para React Router v5 2import { useHistory, useLocation } from "react-router-dom"; 3 4function Login() { 5 // ... 6 const history = useHistory(); 7 const location = useLocation(); 8 const { from } = location.state || { from: { pathname: "/dashboard" } }; 9 10 const handleSubmit = async (e) => { 11 // ... 12 try { 13 await login(email, password); 14 history.replace(from); 15 } catch (error) { 16 // ... 17 } 18 }; 19}

Protección de Rutas Basada en Roles

Para aplicaciones más complejas, es posible que necesites restringir rutas basándote en roles de usuario. Extendamos nuestra protección de rutas para incluir verificaciones de roles:

1function RequireRole({ children, role }) { 2 const { isAuthenticated, hasRole, loading } = useAuth(); 3 4 if (loading) { 5 return <div>Cargando...</div>; 6 } 7 8 if (!isAuthenticated()) { 9 return <Navigate to="/login" />; 10 } 11 12 if (!hasRole(role)) { 13 return <Navigate to="/unauthorized" />; 14 } 15 16 return children; 17} 18 19// Uso en rutas 20<Route path="/admin/dashboard" element={ 21 <RequireRole role="admin"> 22 <AdminDashboard /> 23 </RequireRole> 24} />

Enrutamiento con Layouts para Áreas Protegidas

Para aplicaciones con layouts distintos para secciones autenticadas y no autenticadas, puedes combinar rutas privadas con componentes de layout:

1function AuthenticatedLayout({ children }) { 2 return ( 3 <div className="authenticated-layout"> 4 <Sidebar /> 5 <div className="content-area"> 6 <TopNav /> 7 <main>{children}</main> 8 </div> 9 </div> 10 ); 11} 12 13// En tu configuración de rutas 14<Route path="/dashboard" element={ 15 <RequireAuth> 16 <AuthenticatedLayout> 17 <Dashboard /> 18 </AuthenticatedLayout> 19 </RequireAuth> 20} />

Manejando Protección de Rutas con Rutas Anidadas

Para aplicaciones más complejas con estructuras de rutas anidadas, el modelo de composición de React Router v6 funciona bien:

1<Routes> 2 {/* Rutas públicas */} 3 <Route path="/" element={<PublicLayout />}> 4 <Route index element={<Home />} /> 5 <Route path="about" element={<About />} /> 6 <Route path="login" element={<Login />} /> 7 </Route> 8 9 {/* Rutas protegidas */} 10 <Route path="/app" element={ 11 <RequireAuth> 12 <AuthenticatedLayout /> 13 </RequireAuth> 14 }> 15 <Route index element={<Dashboard />} /> 16 <Route path="profile" element={<Profile />} /> 17 <Route path="settings" element={<Settings />} /> 18 19 {/* Rutas protegidas anidadas con requisitos adicionales de rol */} 20 <Route path="admin" element={ 21 <RequireRole role="admin"> 22 <AdminLayout /> 23 </RequireRole> 24 }> 25 <Route index element={<AdminDashboard />} /> 26 <Route path="users" element={<AdminUsers />} /> 27 </Route> 28 </Route> 29</Routes>

Carga Perezosa de Rutas Protegidas

Para un mejor rendimiento en aplicaciones más grandes, puedes combinar la protección de rutas con carga perezosa (lazy loading):

1import React, { lazy, Suspense } from "react"; 2 3// Componentes cargados perezosamente 4const Dashboard = lazy(() => import("./pages/Dashboard")); 5const Settings = lazy(() => import("./pages/Settings")); 6const Profile = lazy(() => import("./pages/Profile")); 7 8function App() { 9 return ( 10 <BrowserRouter> 11 <Routes> 12 <Route path="/" element={<Home />} /> 13 <Route path="/login" element={<Login />} /> 14 15 <Route path="/app" element={ 16 <RequireAuth> 17 <Suspense fallback={<div>Cargando...</div>}> 18 <Outlet /> 19 </Suspense> 20 </RequireAuth> 21 }> 22 <Route index element={<Dashboard />} /> 23 <Route path="profile" element={<Profile />} /> 24 <Route path="settings" element={<Settings />} /> 25 </Route> 26 </Routes> 27 </BrowserRouter> 28 ); 29}

Conclusión

Las rutas privadas son esenciales para proteger el contenido sensible en aplicaciones React. Al combinar React Router con nuestro contexto de autenticación, hemos creado un sistema robusto para la protección de rutas que puede escalar desde aplicaciones simples hasta sistemas complejos de control de acceso basado en roles.

Los patrones mostrados en esta lección proporcionan una base sólida para implementar guardias de autenticación en tus aplicaciones React, asegurando que solo los usuarios autorizados puedan acceder al contenido protegido mientras se proporciona una experiencia de usuario fluida.