Autenticación y Seguridad
Floutic implementa un sistema de autenticación robusto con múltiples capas de seguridad, siguiendo las mejores prácticas de la industria.
Sistema de Autenticación JWT
Tokens
Floutic utiliza un sistema dual de tokens:
-
Access Token (JWT)
- Almacenado en memoria (no en localStorage)
- Vida corta (15 minutos por defecto)
- Incluye información del usuario y
session_id - Enviado en header
Authorization: Bearer <token>
-
Refresh Token
- Almacenado en cookie HttpOnly
- Vida larga (7 días por defecto)
- Usado para renovar access tokens
- No accesible desde JavaScript
Endpoints de Autenticación
Login Seguro
POST /api/auth/login_secure
Request:
{
"username": "usuario@ejemplo.com",
"password": "contraseña_segura"
}
Headers requeridos:
X-Session-ID: ID único de sesión generado por el cliente
Response:
{
"access_token": "eyJ...",
"csrf_token": "csrf_...",
"user": {
"id": 1,
"email": "usuario@ejemplo.com",
"username": "usuario",
"role": "empresa"
}
}
Cookies establecidas:
refresh_token: HttpOnly, SameSite=Strictcsrf_token: HttpOnly, SameSite=Strict
Refresh Token
POST /api/auth/refresh_secure
Headers requeridos:
X-Session-ID: Debe coincidir con el del token- Cookie:
refresh_token
Response:
{
"access_token": "eyJ...",
"csrf_token": "csrf_..."
}
Verificar Sesión
GET /api/auth/verify_secure
Headers requeridos:
X-Session-ID: Debe coincidir con el del token- Cookie:
refresh_token
Response:
{
"valid": true,
"authenticated": true,
"user": {
"id": 1,
"email": "usuario@ejemplo.com",
"username": "usuario",
"role": "empresa"
},
"access_token": "eyJ...",
"csrf_token": "csrf_..."
}
Logout
POST /api/auth/logout_secure
Headers requeridos:
X-Session-ID- Cookie:
refresh_token X-CSRF-Token: Token CSRF
Response:
{
"message": "Logout successful"
}
Protección CSRF
Implementación (Double Submit Cookie)
Floutic implementa protección CSRF robusta utilizando el patrón Double Submit Cookie:
-
Token CSRF en Cookie
- Generado en login/refresh
- Almacenado en cookie
csrf_token(HttpOnly, SameSite=Strict) - Incluido también en la respuesta JSON (para que el cliente lo lea)
-
Validación en Servidor (
verify_csrf_token)- Todos los requests que modifican estado (POST, PUT, DELETE, PATCH) en endpoints protegidos requieren validación.
- El cliente debe enviar el token en el header
X-CSRF-Token. - El servidor compara criptográficamente (
secrets.compare_digest) el valor de la cookiecsrf_tokencon el headerX-CSRF-Token. - Si no coinciden o faltan, se rechaza con
403 Forbidden.
-
Endpoints Protegidos
- Actualización de perfil (
PUT /me) - Cambio de contraseña
- Cambio de email
- Configuración de webhooks
- Gestión de hitos y pagos
- Actualización de perfil (
-
Renovación
- Token se renueva en cada refresh
- Token se invalida en logout
Uso en Frontend
// El cliente Axios añade automáticamente el CSRF token
const response = await api.post('/api/projects', data);
// Header X-CSRF-Token se añade automáticamente
Session ID Único
Propósito
El session_id único por ventana previene la fuga de sesiones entre ventanas privadas.
Implementación
-
Generación en Cliente
- Cada ventana genera un
session_idúnico - Almacenado en
sessionStorage(aislado entre ventanas) - Formato:
sess_<random>
- Cada ventana genera un
-
Inclusión en JWT
- El
session_idse incluye en el payload del JWT - Validado en cada request
- El
-
Validación en Servidor
login_secure: LeeX-Session-IDdel header, lo incluye en JWTrefresh_secure: Valida quesession_iddel header coincida con el del tokenverify_secure: Valida quesession_iddel header coincida con el del token
Flujo
┌─────────────────────────────────────┐
│ VENTANA 1 │
│ 1. Genera session_id: sess_abc123 │
│ 2. Login → JWT incluye sess_abc123 │
│ 3. Cookie creada con JWT │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ VENTANA 2 │
│ 1. Genera session_id: sess_xyz789 │
│ 2. Aunque tenga cookie compartida...│
│ 3. verify_secure compara: │
│ - Token: sess_abc123 │
│ - Header: sess_xyz789 │
│ - ❌ NO COINCIDEN → RECHAZADO │
└─────────────────────────────────────┘
Cookies Seguras
Configuración
Desarrollo:
domain=None # Sin domain para máximo aislamiento
secure=False # HTTP permitido en localhost
samesite="strict" # Aislamiento máximo entre contextos
httponly=True # No accesible desde JavaScript
Producción:
domain="floutic.com" # Para permitir subdominios
secure=True # Solo HTTPS
samesite="strict" # Aislamiento máximo
httponly=True # No accesible desde JavaScript
Cookies Utilizadas
-
refresh_token
- HttpOnly, SameSite=Strict
- Vida: 7 días (configurable)
- Usado para renovar access tokens
-
csrf_token
- HttpOnly, SameSite=Strict
- Vida: 7 días (configurable)
- Usado para protección CSRF
Sesiones Concurrentes
Límites
- Máximo 10 sesiones concurrentes por usuario individual
- Sin límite para usuarios de empresa (múltiples usuarios pueden estar logueados simultáneamente)
Configuración
MAX_CONCURRENT_SESSIONS = 10 # Configurable via env var
Gestión
- Las sesiones se rastrean en Redis
- Al exceder el límite, la sesión más antigua se invalida
- El usuario recibe notificación de sesión expirada
Rate Limiting
Niveles
- Global: 60 requests/minuto por IP
- Login por Usuario: 5 intentos máximo
- Login por IP: 20 intentos máximo
Implementación
# Global rate limiting
@limiter.limit("60/minute")
# Login rate limiting
@limiter.limit("5/minute", key_func=lambda: f"login_user_{user_id}")
@limiter.limit("20/minute", key_func=lambda: f"login_ip_{ip}")
Respuestas
- 429 Too Many Requests: Cuando se excede el límite
- Retry-After: Header con tiempo de espera
- Frontend muestra mensaje apropiado al usuario
Verificación de Email
Flujo de Registro
- Usuario se registra → Login automático
- Se envía email de verificación (token válido 48 horas)
- Usuario puede usar la plataforma normalmente pero ve banner recordatorio
- Al verificar email → Se envía email de bienvenida
Características
- Sin restricciones: Los usuarios pueden usar la plataforma sin verificar email
- Banner recordatorio: Aparece hasta verificar el email
- Reenvío: Disponible con rate limit (1 cada 2 minutos)
- Token seguro: Tokens hasheados y con expiración
Endpoints
POST /api/auth/verify-email- Verificar email con tokenPOST /api/auth/send-verification-email- Reenviar email de verificación
Más información en Verificación.
Validación de Contraseñas
Requisitos
- Mínimo 8 caracteres
- Al menos una mayúscula
- Al menos una minúscula
- Al menos un número
- Al menos un carácter especial
Hash
- bcrypt con salt rounds = 12
- Nunca se almacenan contraseñas en texto plano
RBAC (Role-Based Access Control)
Sistema Multi-Rol
Floutic implementa un sistema de roles múltiples que permite a los usuarios tener varios roles simultáneamente:
- Un usuario puede ser empresa Y experto al mismo tiempo
- Cada rol tiene su propio perfil independiente con verificación separada
- El usuario puede cambiar entre roles en cualquier momento
- El rol activo (
active_role) determina qué dashboard y funcionalidades están disponibles
Roles Disponibles
- admin: Acceso completo al sistema (solo puede ser asignado manualmente)
- empresa: Gestión de proyectos y pagos
- experto: Aplicación a proyectos y gestión de hitos
Estructura de Roles
{
"roles": ["empresa", "experto"], // Array de roles del usuario
"active_role": "empresa" // Rol actualmente activo
}
Gestión de Roles
Registro:
- Al registrarse, el usuario puede seleccionar uno o ambos roles (empresa y/o experto)
- Se crea un perfil inicial para el primer rol seleccionado
- El
active_rolese establece al primer rol de la lista
Añadir Rol:
- Endpoint:
POST /api/auth/add-role - Permite añadir un nuevo rol al usuario autenticado
- Requiere crear el perfil correspondiente después de añadir el rol
Cambiar Rol Activo:
- Endpoint:
POST /api/auth/switch-role - Cambia el
active_roledel usuario - Redirige automáticamente al dashboard del nuevo rol
Implementación
# Verificar si el usuario tiene un rol específico
if "admin" in user.roles:
# Usuario es admin
pass
# Verificar rol activo
if user.active_role == "empresa":
# Usuario está usando el rol de empresa
pass
# Dependencias de FastAPI
@router.get("/admin/users")
async def get_users(current_user: User = Depends(get_current_admin_user)):
# get_current_admin_user verifica que "admin" esté en user.roles
pass
@router.get("/projects/me")
async def get_my_projects(current_user: User = Depends(get_current_company_user)):
# get_current_company_user verifica que active_role == "empresa"
pass
Perfiles Multi-Rol
- Cada rol tiene su propio perfil independiente
- Un usuario con roles
["empresa", "experto"]puede tener:- Un perfil de empresa (con verificación independiente)
- Un perfil de experto (con verificación independiente)
- El endpoint
/api/profiles/medevuelve el perfil delactive_roleactual - Cada perfil puede tener diferentes estados de verificación
Más Información
- Cookies - Seguridad de cookies y sesiones
- Headers - Security headers implementados
- Verificación - Sistema de verificación
- Frontend - Autenticación - Autenticación en frontend