Integración con Firebase Auth
Esta guía le mostrará cómo integrar MichiRouter con Firebase Authentication para crear un sistema de rutas protegidas robusto y fácil de usar.
Configuración inicial
Sección titulada «Configuración inicial»1. Instalar dependencias
Sección titulada «1. Instalar dependencias»npm install @arielgonzaguer/michi-router firebase2. Configurar Firebase
Sección titulada «2. Configurar Firebase»Crea un archivo firebase.js en tu proyecto:
import { initializeApp } from 'firebase/app';import { getAuth } from 'firebase/auth';import { getFirestore } from 'firebase/firestore';
const firebaseConfig = { apiKey: "tu-api-key", authDomain: "tu-proyecto.firebaseapp.com", projectId: "tu-proyecto-id", storageBucket: "tu-proyecto.appspot.com", messagingSenderId: "123456789", appId: "tu-app-id"};
const app = initializeApp(firebaseConfig);export const auth = getAuth(app);export const db = getFirestore(app);Store de autenticación con Zustand
Sección titulada «Store de autenticación con Zustand»Crear un store para manejar el estado de autenticación:
import { create } from 'zustand';import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged, User} from 'firebase/auth';import { auth } from '../firebase';
interface AuthState { user: User | null; isLoading: boolean; error: string | null; signIn: (email: string, password: string) => Promise<void>; signUp: (email: string, password: string) => Promise<void>; logout: () => Promise<void>; initializeAuth: () => void;}
const useAuthStore = create<AuthState>((set, get) => ({ user: null, isLoading: true, // Importante: iniciar en true error: null,
signIn: async (email: string, password: string) => { try { set({ isLoading: true, error: null }); await signInWithEmailAndPassword(auth, email, password); } catch (error: any) { set({ error: error.message, isLoading: false }); } },
signUp: async (email: string, password: string) => { try { set({ isLoading: true, error: null }); await createUserWithEmailAndPassword(auth, email, password); } catch (error: any) { set({ error: error.message, isLoading: false }); } },
logout: async () => { try { await signOut(auth); } catch (error: any) { set({ error: error.message }); } },
initializeAuth: () => { onAuthStateChanged(auth, (user) => { set({ user, isLoading: false, error: null }); }); }}));
export default useAuthStore;Componente de login
Sección titulada «Componente de login»import React, { useState } from 'react';import { useNavigate } from '@arielgonzaguer/michi-router';import useAuthStore from '../store/useAuthStore';
export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const { signIn, isLoading, error } = useAuthStore(); const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await signIn(email, password); navigate('/dashboard'); // Redirigir tras login exitoso } catch (error) { console.error('Error al iniciar sesión:', error); } };
return ( <div className="min-h-screen flex items-center justify-center"> <form onSubmit={handleSubmit} className="max-w-md mx-auto space-y-4"> <h2 className="text-2xl font-bold text-center">Iniciar Sesión</h2>
{error && ( <div className="p-3 text-red-600 bg-red-100 rounded"> {error} </div> )}
<input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} className="w-full p-3 border rounded" required />
<input type="password" placeholder="Contraseña" value={password} onChange={(e) => setPassword(e.target.value)} className="w-full p-3 border rounded" required />
<button type="submit" disabled={isLoading} className="w-full p-3 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50" > {isLoading ? 'Iniciando sesión...' : 'Iniciar Sesión'} </button> </form> </div> );}Configuración del Router Principal
Sección titulada «Configuración del Router Principal»import React, { useEffect } from 'react';import { RouterProvider, Protected} from '@arielgonzaguer/michi-router';import useAuthStore from './store/useAuthStore';
// Componentesimport Home from './components/Home';import Login from './components/Login';import Dashboard from './components/Dashboard';import Profile from './components/Profile';import NotFound from './components/NotFound';import LoadingSpinner from './components/LoadingSpinner';
export default function MichiRouter() { const authStore = useAuthStore();
// Inicializar listener de autenticación useEffect(() => { authStore.initializeAuth(); }, []);
// Configuración para rutas protegidas const protectedConfig = { states: { user: authStore.user, isLoading: authStore.isLoading }, redirectionPath: "/login", loadingComponent: <LoadingSpinner />, defaultMessage: "Verificando autenticación..." };
const routes = [ { path: "/", component: <Home /> }, { path: "/login", component: <Login /> }, { path: "/dashboard", component: ( <Protected configObject={protectedConfig}> <Dashboard /> </Protected> ) }, { path: "/profile", component: ( <Protected configObject={protectedConfig}> <Profile /> </Protected> ) } ];
return ( <RouterProvider routes={routes}> <NotFound /> </RouterProvider> );}Componente Dashboard Protegido
Sección titulada «Componente Dashboard Protegido»import React from 'react';import { useNavigate } from '@arielgonzaguer/michi-router';import useAuthStore from '../store/useAuthStore';
export default function Dashboard() { const { user, logout } = useAuthStore(); const navigate = useNavigate();
const handleLogout = async () => { await logout(); navigate('/'); };
return ( <div className="p-6"> <div className="flex justify-between items-center mb-6"> <h1 className="text-3xl font-bold">Dashboard</h1> <button onClick={handleLogout} className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Cerrar Sesión </button> </div>
<div className="bg-white p-6 rounded-lg shadow"> <h2 className="text-xl font-semibold mb-4">Bienvenido</h2> <p>Email: {user?.email}</p> <p>UID: {user?.uid}</p> </div> </div> );}Reglas de seguridad en Firestore
Sección titulada «Reglas de seguridad en Firestore»Para completar la seguridad, configure las reglas de Firestore:
rules_version = '2';service cloud.firestore { match /databases/{database}/documents { // Permitir acceso solo a usuarios autenticados match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
// Datos públicos (si los hay) match /public/{document} { allow read: if true; allow write: if request.auth != null; }
// Denegar todo lo demás match /{document=**} { allow read, write: if false; } }}Mejores prácticas
Sección titulada «Mejores prácticas»1. Manejo de estado de carga
Sección titulada «1. Manejo de estado de carga»- Siempre inicializa
isLoadingentruehasta verificar la sesión - Usa el listener
onAuthStateChangedpara manejar cambios de estado
2. Redirecciones inteligentes
Sección titulada «2. Redirecciones inteligentes»// Redirigir usuarios autenticados lejos del loginuseEffect(() => { if (!isLoading && user && location.pathname === '/login') { navigate('/dashboard'); }}, [isLoading, user, location.pathname]);3. Persistencia de rutas
Sección titulada «3. Persistencia de rutas»// Guardar la ruta donde el usuario quería irconst protectedConfig = { states: { user: authStore.user, isLoading: authStore.isLoading }, redirectionPath: `/login?redirect=${encodeURIComponent(location.pathname)}`, // ...};4. Manejo de errores
Sección titulada «4. Manejo de errores»// En el componente Loginconst urlParams = new URLSearchParams(location.search);const redirectTo = urlParams.get('redirect') || '/dashboard';
// Tras login exitosonavigate(redirectTo);Ejemplo completo de uso
Sección titulada «Ejemplo completo de uso»Este setup le proporciona:
- ✅ Autenticación completa con Firebase
- ✅ Rutas protegidas con MichiRouter
- ✅ Estado de carga apropiado
- ✅ Redirecciones automáticas
- ✅ Manejo de errores
- ✅ Experiencia de usuario fluida
El componente Protected en este ejemplo se encarga automáticamente de:
- Mostrar un spinner mientras se verifica la autenticación
- Redirigir a login si no hay usuario
- Renderizar el contenido protegido si el usuario está autenticado
Esta integración aprovecha al máximo la simplicidad de MichiRouter manteniendo toda la funcionalidad robusta de Firebase Auth.