¿Cómo implementar un flujo de autenticación y autorización en aplicaciones?
En este emocionante recorrido, vamos a construir un flujo completo de navegación para aplicaciones que requieren autenticación y autorización utilizando React Router DOM. Aprenderemos a manejar rutas privadas y proteger contenido según el estado de autenticación del usuario.
¿Qué es la autenticación y autorización en aplicaciones web?
Autenticación: Es el proceso de verificar la identidad de un usuario. Un ejemplo común es el inicio de sesión en una aplicación.
Autorización: Es la verificación de los permisos que tiene un usuario autenticado para acceder a ciertos recursos o realizar acciones específicas.
En nuestro sistema, las rutas privadas estarán accesibles solo para usuarios autenticados, mientras que otras funcionalidades podrán variar dependiendo del rol del usuario.
¿Cómo simular la autenticación?
Hemos creado un sistema de autenticación ficticio para ilustrar cómo se podría implementar un flujo de autenticación. Inicialmente, esto se gestiona con React Hooks para simular un "login falso".
Pasos para crear la autenticación:
Crear componentes de Login y Logout:
Desarrollar una estructura básica para las páginas de login y logout, replicando componentes de ejemplo, y aplicar la lógica de React Router para gestionar las rutas.
Implementar un formulario de login:
Añadir un formulario que capture el nombre de usuario e integre un estado básico con useState para gestionar los cambios en el input.
importReact,{ useState }from'react';functionLoginPage(){const[username, setUsername]=useState('');consthandleLogin=(event)=>{ event.preventDefault();console.log(username);// Simulación de autenticación};return(<formonSubmit={handleLogin}><label>Escribe tu nombre de usuario</label><inputtype="text"value={username}onChange={(e)=>setUsername(e.target.value)}/><buttontype="submit">Entrar</button></form>);}
Crear un contexto para la autenticación:
Utilizar React.createContext para establecer un contexto que gestione el estado de autenticación y proporcionar funciones de login y logout.
¿Cómo integrar autenticación con rutas protegidas?
Una vez que se ha establecido un contexto para la autenticación, podemos utilizarlo para proteger rutas y condicionar la navegación según el estado de login del usuario.
Paso a paso:
Utilizar el AuthProvider en tu aplicación:
Encapsular la estructura de la aplicación dentro del AuthProvider para que todas las rutas tengan acceso al contexto de autenticación.
import{AuthProvider}from'./auth';functionApp(){return(<AuthProvider><HashRouter>{/* Rutas de tu aplicación */}</HashRouter></AuthProvider>);}
Proteger rutas sensibles:
Condicionar el acceso a ciertas rutas verificando el estado de usuario en el contexto. Si no está autenticado, redirigir al login.
Este enfoque garantiza que los usuarios no autenticados sean llevados a login si intentan acceder a áreas restringidas, y mantiene la interfaz limpia y adecuada a su estado. Este es solo el inicio de un sistema de autenticación robusto; las mejoras podrían incluir gestión de roles más compleja o integración con un backend para autenticación verificada. ¡Sigue explorando más sobre este tema para llevar tus proyectos al siguiente nivel!
Ahora vamos a aprender como desplegar contenido en base a la Autenticación de usuarios, ósea que no todo el contenido de una aplicación va a ser público pata todos los usuarios, sino que tengamos algunas rutas privadas para personas que no se hayan autenticado, ósea hecho login y así mismo no mostrar contenido que es innecesario para personas que ya están registradas, como el mismo registro de usuario o contenido introductorio.
Vamos a hacer contenido que dependiendo el usuario se muestre o no se muestre, y también permitir tener privilegios o permisos dependiendo del rol que tengas.
Vamos entonces a crear nuevas rutas que nos permitan hacer todo esto:
importReactfrom'react';functionLoginPage(){/* Para manejar el registro de usuario con el formilario haremos
uso del estado de React */const[username, setUsername]=React.useState('');/* Esta es la función que se ejecutará cuando ocurra el evento de
Submit */constlogin=(e)=>{ e.preventDefault();}// Aquí creamos un formulario para poder auteticarnosreturn(<><h2>Login</h2><formonSubmit={login}><label>Escribe tu nombre de usuario</label><inputvalue={username}onChange={e=>setUsername(e.target.value)}/><buttontype="submit">Entrar</button></form></>);}export{LoginPage}
LogoutPage.js
importReactfrom'react';functionLogoutPage(){constlogout=(e)=>{ e.preventDefault();}return(<><h2>Logout</h2><formonSubmit={logout}><label>¿Segurx de que quieres salir?</label><buttontype="submit">Salir</button></form></>);}export{LogoutPage}
En nuestro Menu.js vamos a crear dos nuevas rutas en nuestro Array routes:
Si lo pensamos bien, no nos debería salir el componente Profile y Logout sin antes haber hecho un Login, vamos a trabajar entonces en esa lógica creando un archivo de autenticación:
...import{AuthProvider, useAuth }from'./Components/auth/auth'functionApp(){return(<>{/* En este caso AuthProvider tiene que ir dentro de
HastRouter en caso de que AuthProvider necesite de algún
método o contenido que pueda proveer HashRouter */}<HashRouter><AuthProvider><Menu/><Routes><Routepath='/'element={<HomePage/>}/><Routepath='/blog'element={<BlogPage/>}><Routepath=':slug'element={<BlogPost/>}/></Route><Routepath='/login'element={<LoginPage/>}/><Routepath='/logout'element={<LogoutPage/>}/><Routepath='/profile'element={<ProfilePage/>}/><Routepath='/*'element={<p>Not Found</p>}/></Routes></AuthProvider></HashRouter></>)}...
En este punto, en LoginPage.js ya podemos empezar a utilizar nuestro useAuth:
...functionAuthProvider({ children }){/* Siguiendo la lógica, si user es null significa que no estamos
autenticados */const[user, setUser]=React.useState(null);// Ahora necesitamos darle un valor a nuestro usuarioconstlogin=({ username })=>{setUser({ username });}// De la misma forma, debemos poder cerrar la sesiónconstlogout=()=>{setUser(null);}...}
Ahora en LoginPage.js nuestro useAuth nos debe dar acceso al usuario y sus métodos:
Vamos de nuevo a auth.js y vamos a complementar la lógica:
functionAuthProvider({ children }){const navigate =useNavigate();constlogin=({ username })=>{setUser({ username });/* Ahora cada vez que hagamos login nos redireccionará a la
página de profile */navigate('/profile');}constlogout=()=>{setUser(null);/* Aquí haremos redirect a la página principal */navigate('/');}
Pero para esto debemos añadir el contexto de autentificación a LogoutPage.js:
Ahora en nuestra ProfilePage.js debemos completar esta lógica:
import{ useAuth }from'../../auth/auth';functionProfilePage(){const auth =useAuth();return(/* Ahora si nosotros nos registramos deberíamos poder ver
nuestro nombre de usuario en nuestra página de perfil */<><h2>Perfil</h2><h2>Welcome {auth.user.username}</h2></>);}
Y listo, ahora solo nos queda el que no podamos entrar a profile sino hasta que hayamos hecho login, lo cual haremos a continuación.
Excelente resumen amigo. Así se puede entender mejor despues de ver la clase
gracias es realmente de mucha ayuda <3
Por si alguien tien este error: usando Vite
[vite]Internal server error:Failed to parse source forimport analysis because the content contains invalid JS syntax.If you are using JSX, make sure to name the file with the .jsx or .tsx extension.Plugin: vite:import-analysis
File:/home/naiper/cursos/33-react-router/src/helpers/auth.js33| logout
34|};35|return<AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;|^36|}37| at formatError(file:///home/naiper/cursos/33-react-router/node_modules/vite/dist/node/chunks/dep-db16f19c.js:40862:46) at TransformContext.error(file:///home/naiper/cursos/33-react-router/node_modules/vite/dist/node/chunks/dep-db16f19c.js:40858:19) at TransformContext.transform(file:///home/naiper/cursos/33-react-router/node_modules/vite/dist/node/chunks/dep-db16f19c.js:37530:22) at asyncObject.transform(file:///home/naiper/cursos/33-react-router/node_modules/vite/dist/node/chunks/dep-db16f19c.js:41111:30) at asyncloadAndTransform(file:///home/naiper/cursos/33-react-router/node_modules/vite/dist/node/chunks/dep-db16f19c.js:37373:29)
Lo que hice fue cambiar el archivo auth.js por un auth.jsx
Otra solución que encontré fue cambiar la configuración del vite.config.js
Agregué una regla: "Enable hmr overlay with vite react and ts"
Pero lamentablemente no me sirvió, sólamente me quitó el error en Vite pero me dejó en consola.
Es porque en vite es requerido usar JSX
me funcionó tu solución y además al momento de importar auth.jsx en app.jsx y en LoginPage.jsx hay que importarlo con con la extensión .jsx
Así
import{ useAuth }from"./auth.jsx";
Decidí organizar los archivos de esta manera.
Me too!, no podia soportar tando desorden, Desus Meus!
Mi Profe Facherito
Gracias Juan. De hecho en primera instancia creí que era mejor que el AuthProvider fuera el componente de más arriba. Pero luego de tu explicación, comprendo qué lógica debo tener a la hora de definir cómo organizar estos Providers.
Cuando relacione el label con el input utilizando for, react me sugirió cambiarlo por htmlFor.
<label htmlFor='input-login'>Escribe tu nombre de usuario:</label><input
value={username} onChange={e=>setUsername(e.target.value)} id='input-login'/>
Por si alguien también está haciendo el proyecto con TypeScript, comparto cómo me quedó auth.tsx:
import{ useAuth }from"./useAuth"constProfilePage=()=>{const{ user }=useAuth();return(<><h2>Perfil</h2><p>Welcome,{ user?.username}</p></>)}export{ProfilePage}
Podrían verificar que que exista un usuario con:
<h2>{auth.user?.username}</h2>
el operador '?.' verifica que dentro de objeto auth.user exista username, si no existe devolverá undefined y el h1 en la pagina estará vació.
.
o podrían hacer una condicional para mostrar un username por default:
El operador de coalescencia nula?? solo devuelve el valor del lado derecho ('User936') si el valor del lado izquierdo es null o undefined.
Y si no existe un 'username' en 'auth.user?.username' devolverá undefined
Esta genial el curso Juan pero más de 20 min de fake auth no me sirve. :(
Esta clase estuvo bien intensa, tuve que mirarla 3 veces para poder entender bien.
Por qué se pasa por parámetro ({ username }) y no simplemente (username) sin los curly brackets?
Lo he puesto así en todas las llamadas al auth.login() y en las partes donde hago uso del username y funciona perfecto
Solo por gusto personal. Me gusta mucho la sintaxis de los objetos y RORO, sobre todo cuando son muuuuchas propiedades. En este caso que solo es una da lo mismo. :D
interesante dato, no lo habia notado
Basicamente lo que hace el metodo "login" es setear el estado de user name con el nombre de usuario que le pasamos, que viene del input
El metodo "logout" lo que hace es setear ese estado como null
Y todo esta guardado en el contexto al cual tenemos acceso con el hook de useAuth
Así lo compartimos con todos los componentes
Como es el atajo para las importaciones?
Control + Espacio
AuthConsumer pensé en OutConsumer uwu, si alguien jugaba NBA en PlayStation allá por el año 2012 de repente conoce a ese Youtuber jajaja
useAuth: login y logout
.
Empezamos a desarrollar un flujo de navegación completo para aplicaciones que tengan autenticación y autorización. Esto significa que no todos los usuarios podrán tener acceso a rutas que deberían de ser privadas u ocultas si no se han autenticado. Incluso si ya hicieron login hay cosas que no queremos que vean, contenido que no cualquier usuario debe poder ver por cuestiones de autorización.
.
Lo primero que haremos en App es crear 2 nuevas rutas /login y logout con sus respectivos componentes a renderizar.
.
.
Vamos a necesitar un archivo auth.js el cual nos va permitir simular nuestro flujo de autenticación. Su objetivo principal es exportar un objeto que contenga nuestro AuthProvider y nuestro react hook useAuth.
.
En este archivo creamos un contexto para la autenticación. Dentro del provider de este contexto por medio de la propiedad value le pasamos un objeto auth que va a contener el estado de nuestro usuario user que por defecto es null cuando no está logeado; también contiene a las funciones de login y logout que lo que hacen es setear el estado del usuario y navigar como corresponda mediante useNavigate.
.
.
El react hook useAuth lo que hace es simplemente llamar al contexto, guardarlo y exportarlo en una variable.
.
Finalmente, el AuthProvider es un componente que nos permitirá encapsular el contenido de nuestro HashRouter en App. Por lo que tendrá acceso a la información que puede proporcionar HashRouter, pero a la vez los componentes contenidos por AuthProvider como Menu o las rutas, tendrán acceso a las propiedades del objeto auth que proporciona el provider.
.
.
Posteriormente, creamos o modificamos los componentes necesarios para este flujo, empezando por LoginPage, LogoutPage y Profile.
.
En LoginPage lo que podemos observar es que tenemos un pequeño formulario que nos permite ingresar el username, y mediante el react hook useAuth al realizar el evento de submit utilizaremos la función login que a su vez llama a auth.login para setear el valor del nombre de usuario y redirigirnos a la página de Profile de este.
.
importReactfrom'react';import{ useAuth }from'./auth';functionLoginPage(){const auth =useAuth();const[username, setUsername]=React.useState('');constlogin=(e)=>{ e.preventDefault(); auth.login({ username });};return(<><h2>Login</h2><formonSubmit={login}><label>Escribe tu nombre de usuario:</label><inputvalue={username}onChange={e=>setUsername(e.target.value)}/><buttontype="submit">Entrar</button></form></>);}export{LoginPage};
.
En LogoutPage también se tiene un pequeño formulario que al realizar el evento de submit en este caso va a setear el estado del usuario en null y redirigirnos a la página principal / por medio del react hook useAuth y la función logout que a su vez utiliza a auth.logout.
.
importReactfrom'react';import{ useAuth }from'./auth';functionLogoutPage(){const auth =useAuth();constlogout=(e)=>{ e.preventDefault(); auth.logout();};return(<><h2>Logout</h2><formonSubmit={logout}><label>¿Segura de que quieras salir?</label><buttontype="submit">Salir</button></form></>);}export{LogoutPage};
.
Finalmente para completar esta parte del flujo tenemos a ProfilePage que va renderizar la página de perfil de usuario. Este utiliza al rect hook useAuth para recuperar la información del nombre de usuario y poder renderizarlo.
.
Sin embargo dará error si no se ha pasado a través de las etapas del flujo adecuadamente. Por lo que aún se debe implementar una forma de proteger las rutas para evitar este tipo de error, o condicionar el renderizado de forma que si el usuario es null entonces manejar el renderizado de otra manera.
.
importReactfrom"react";functionLoginPage(){**const[username, setUsername]=React.useState('');constlogin=(e)=>{ e.preventDefault();console.log(username);}**return(<> **<h2>Login</h2><formonSubmit={login}><label>Escribe tu nombre de usuario:</label><inputvalue={username}onChange={e=>setUsername(e.target.value)}/><buttontype="submit">Entrar</button></form>**
</>);}export{LoginPage};
importReactfrom"react";functionLogoutPage(){**constlogout=(e)=>{ e.preventDefault();console.log('Logout');}**return(**<><h2>Logout</h2><formonSubmit={logout}><label>¿Seguro de que quieres salir?</label><buttontype="submit">Salir</button></form></>**);}export{LogoutPage};
import"./App.css";// more imports...**import{AuthProvider}from"./api/auth";**functionApp(){return(<><HashRouter> **<AuthProvider>**
<Menu/><Routes><Routepath="/"element={<HomePage/>}/>{/* More routes... */}</Routes> **</AuthProvider>**
</HashRouter></>);}exportdefaultApp;
....constAuthContext=React.createContext()functionAuthProvider({ children }){**const navigate =useNavigate()const[user, setUser]=useState()constlogin=({ username })=>{setUser({ username })navigate('/profile')}constlogout=()=>{setUser(null);navigate('/')}const auth ={ user, login, logout };**return(<AuthContext.Providervalue={auth}>{ children }</AuthContext.Provider>)}...
importReactfrom"react"import{ useAuth }from"../api/auth"functionLoginPage(){**const auth =useAuth()**const[username, setUsername]=React.useState('');constlogin=(e)=>{ e.preventDefault()**auth.login({ username })**}return(<><h2>Login</h2><formonSubmit={login}><label>Escribe tu nombre de usuario:</label><inputvalue={username}onChange={e=>setUsername(e.target.value)}/><buttontype="submit">Entrar</button></form></>);}export{LoginPage};
importReactfrom"react";import{ useAuth }from"../api/auth";functionLogoutPage(){**const auth =useAuth()**constlogout=(e)=>{ e.preventDefault();**auth.logout()**}return(<><h2>Logout</h2><formonSubmit={logout}><label>¿Seguro de que quieres salir?</label><buttontype="submit">Salir</button></form></>);}export{LogoutPage};
<aside>
💡 En la siguiente clase manejaremos las rutas que seran privadas, es decir las que necesitarán autentificación para ingresar; y las rutas públicas, que serán las de autentificación.
</aside>
🔐 Autenticación y Autorización en React Router DOM
importReact,{ createContext, useState, useContext }from'react';constAuthContext=createContext();exportconstAuthProvider=({ children })=>{const[user, setUser]=useState(null);constlogin=(username)=>{setUser({ username });};constlogout=()=>{setUser(null);};return(<AuthContext.Provider value={{ user, login, logout }}>{children}</AuthContext.Provider>);};// Hook personalizado para acceder al contextoexportconstuseAuth=()=>useContext(AuthContext);
📝 Datos clave:
✅ Usa createContext para estado global
✅ useState(null) → usuario no autenticado
✅ Hook personalizado useAuth() para acceso fácil
2️⃣ Componente de Login
javascript
importReact,{ useState }from'react';import{ useNavigate }from'react-router-dom';import{ useAuth }from'./auth';functionLoginPage(){const[username, setUsername]=useState('');const auth =useAuth();const navigate =useNavigate();consthandleLogin=(event)=>{ event.preventDefault();if(username.trim()){ auth.login(username);navigate('/profile');// Redirige después del login}};return(<div><h2>🔐 IniciarSesión</h2><form onSubmit={handleLogin}><label>Usuario:</label><input
type="text" value={username} onChange={(e)=>setUsername(e.target.value)} placeholder="Escribe tu nombre"/><button type="submit">Entrar</button></form></div>);}
3️⃣ Componente de Ruta Protegida
javascript
import{Navigate}from'react-router-dom';import{ useAuth }from'./auth';functionProtectedRoute({ children }){const auth =useAuth();// Si no hay usuario, redirige al loginif(!auth.user){return<Navigate to="/login" replace />;}// Si está autenticado, muestra el contenidoreturn children;}exportdefaultProtectedRoute;
functionNavigation(){const auth =useAuth();return(<nav><Link to="/">🏠 Inicio</Link>{/* Mostrar según estado de autenticación */}{!auth.user?(<Link to="/login">🔐 Login</Link>):(<><Link to="/profile">👤 Perfil</Link><Link to="/dashboard">📊 Dashboard</Link><button onClick={auth.logout}>🚪 Logout</button></>)}</nav>);}
🎮 Ejemplo Interactivo Completo
Flujo de Usuario
1️⃣ Usuario visita /profile sin login
↓
2️⃣ ProtectedRoute detecta: auth.user===null ↓
3️⃣ Redirige automáticamente a /login
↓
4️⃣ Usuario ingresa credenciales
↓
5️⃣ auth.login(username) actualiza el contexto
↓
6️⃣ navigate('/profile') redirige al perfil
↓
7️⃣ ProtectedRoute detecta: auth.user!==null ↓
8️⃣ ✅ Muestra el contenido protegido
📊 Casos de Uso Avanzados
🎭 Autorización por Roles
javascript
constAuthContext=createContext();exportconstAuthProvider=({ children })=>{const[user, setUser]=useState(null);constlogin=(username, role)=>{setUser({ username, role });// Agrega el rol};return(<AuthContext.Provider value={{ user, login, logout }}>{children}</AuthContext.Provider>);};// Componente para rutas de adminfunctionAdminRoute({ children }){const auth =useAuth();if(!auth.user){return<Navigate to="/login"/>;}if(auth.user.role!=='admin'){return<Navigate to="/unauthorized"/>;// Página de acceso denegado}return children;}
React Router v6: Usa <Navigate> en lugar de <Redirect>
Estado inicial: useState(null) para usuario no autenticado
Persistencia: LocalStorage para mantener sesión
Seguridad: ⚠️ Esta es solo autenticación frontend, siempre validar en el backend
profe ayuda, tengo una pregunta, porque cuando estoy haciendo el login, sucede todo normal, pero puse que me imprimiera en la consola el user que acabo de cambiar, pero, pongo mi nombre en el input, luego le doy a entrar y aparece en la pantalla welcome y mi nombre, pero en la consola que puse que me imprimiera el user, me sale null, o el valor anterior de user, aqui te muestro
asi queda mi pagina y la consola despues de hacer el login, si funciona cuando le pasa el nombre al parrafo al html, pero a la consola pasa el anterior valor, ayudame a entender porque porfavor, y lo otro es que estoy usando react router v6.8, y la verdad cambio bastante, por eso estoy usando unas cuantas cosas distintas
Hola Nicolas, segun pude ver, estas imprimiendo en consola el user que si te fijas lo tienes declarado en el useState como null al principio cuando esta clase se llama. En ves de imprimir en consola el user, deberías de imprimir el userName. ahí si te funcionará correctamente.