Aplicación web moderna de autenticación con MFA construida con React, TypeScript, Vite y Tailwind CSS.
- React 18: Framework UI moderno con hooks
- TypeScript: Tipado estático para mayor seguridad
- Tailwind CSS: Estilos utility-first responsive
- Vite: Bundler ultra-rápido para desarrollo
- React Router: Navegación SPA con rutas protegidas
- React Hook Form: Manejo de formularios performante
- Zod: Validación de esquemas TypeScript-first
- Axios: Cliente HTTP con interceptores
- MFA Support: Interfaz completa para autenticación de dos factores
- React 18: Biblioteca UI con concurrent features
- TypeScript: Superset de JavaScript con tipos
- Vite: Build tool y dev server
- Tailwind CSS: Framework CSS utility-first
- React Router DOM: Enrutamiento client-side
- React Hook Form: Librería de formularios
- Zod: Validación y parsing de schemas
- Axios: Cliente HTTP
- ESLint + Prettier: Linting y formateo de código
frontend/
├── public/
│ └── vite.svg # Assets estáticos
├── src/
│ ├── components/ # Componentes reutilizables
│ ├── context/
│ │ └── AuthContext.tsx # Context para autenticación global
│ ├── hooks/ # Custom hooks
│ ├── pages/
│ │ ├── LoginPage.tsx # Página de login
│ │ ├── RegisterPage.tsx # Página de registro
│ │ ├── DashboardPage.tsx# Dashboard principal
│ │ └── MfaSetupPage.tsx # Configuración de MFA
│ ├── services/
│ │ └── api.ts # Cliente API y servicios
│ ├── types/
│ │ └── auth.ts # Tipos TypeScript
│ ├── utils/ # Utilidades
│ ├── App.tsx # Componente principal
│ ├── main.tsx # Punto de entrada
│ └── index.css # Estilos globales
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
└── eslint.config.js
- Node.js 18+
- npm o yarn
- Clonar el repositorio
git clone <tu-repo>
cd frontend- Instalar dependencias
npm install- Configurar variables de entorno
# Crear .env.local
VITE_API_URL=http://localhost:8000- Ejecutar en desarrollo
npm run dev- Abrir en navegador
http://localhost:5173
# Desarrollo
npm run dev
# Build de producción
npm run build
# Preview del build
npm run preview
# Linting
npm run lint
npm run lint:fix
# Formateo
npm run format
npm run format:check// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
react(),
tailwindcss(),
],
server: {
port: 5173,
host: true
}
})// tailwind.config.js
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}- Registro: Formulario con validación completa
- Login: Soporte para login con y sin MFA
- Logout: Limpieza de estado y tokens
- Rutas Protegidas: Redirección automática según estado de auth
- Setup Wizard: Proceso guiado de configuración
- QR Code: Generación y display para apps de autenticación
- Verificación: Validación de códigos TOTP
- Códigos de Respaldo: Display y descarga de códigos de emergencia
// AuthContext.tsx
interface AuthContextType {
user: User | null;
token: string | null;
isLoading: boolean;
isAuthenticated: boolean;
login: (token: string) => Promise<void>;
logout: () => void;
updateUser: (user: User) => void;
}// Ejemplo con Zod y React Hook Form
const loginSchema = z.object({
email: z.string().email('Email inválido'),
password: z.string().min(6, 'Mínimo 6 caracteres'),
mfa_code: z.string().optional(),
});
const { register, handleSubmit, formState: { errors } } = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});/- Redirección al dashboard o login/login- Página de inicio de sesión/register- Página de registro/dashboard- Dashboard principal (protegida)/mfa/setup- Configuración de MFA (protegida)
Proveedor de contexto global para el estado de autenticación:
<AuthProvider>
<App />
</AuthProvider>Componente para proteger rutas que requieren autenticación:
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>Todos los formularios utilizan:
- React Hook Form para manejo de estado
- Zod para validación
- Tailwind para estilos
- Manejo de errores consistente
Utility classes para desarrollo rápido:
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors">
Botón
</button>Diseño mobile-first con breakpoints:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">Indicadores visuales para mejor UX:
{isLoading ? (
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
) : (
'Continuar'
)}const api = axios.create({
baseURL: 'http://localhost:8000',
headers: {
'Content-Type': 'application/json',
},
});
// Interceptor para tokens automáticos
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});export const authService = {
register: async (data: RegisterData): Promise<User> => {
const response = await api.post('/auth/register', data);
return response.data;
},
login: async (credentials: LoginCredentials): Promise<AuthResponse> => {
const response = await api.post('/auth/login', credentials);
return response.data;
}
};npm run buildGenera archivos optimizados en /dist
# netlify.toml
[build]
publish = "dist"
command = "npm run build"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]npm install -D @testing-library/react @testing-library/jest-dom jest// Ejemplo de test
import { render, screen } from '@testing-library/react';
import LoginPage from './LoginPage';
test('renders login form', () => {
render(<LoginPage />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
});interface Props {
title: string;
children: React.ReactNode;
}
const Component: React.FC<Props> = ({ title, children }) => {
return (
<div>
<h1>{title}</h1>
{children}
</div>
);
};
export default Component;const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};try {
await authService.login(credentials);
} catch (error) {
if (error.response?.status === 401) {
setError('Credenciales inválidas');
} else {
setError('Error de conexión');
}
}- Fork el proyecto
- Crear branch para feature (
git checkout -b feature/nueva-feature) - Commit cambios (
git commit -am 'Agregar nueva feature') - Push al branch (
git push origin feature/nueva-feature) - Crear Pull Request
Este proyecto está bajo la licencia MIT. Ver LICENSE para más detalles.
Jorge Chávez - kubos777