8.2 KiB
JWT Authentication for VoxPopuli Users API
Overview
La API de Usuarios de VoxPopuli ahora incluye autenticación basada en JSON Web Tokens (JWT). Esta documentación describe cómo usar e implementar esta característica.
Features
✅ Autenticación segura con JWT - Tokens firmados con HS256 ✅ Hashing de contraseñas - Usando bcrypt para máxima seguridad ✅ Contraseña por defecto - "passwd123" para usuarios sin contraseña especificada ✅ Expiración configurable - Tokens con tiempo de vida configurable (default: 24 horas)
Configuration
Environment Variables
Agregue las siguientes variables al archivo .env:
# JWT Configuration
JWT_SECRET_KEY=your-super-secret-key-change-in-production
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24
Importante: En producción, cambiar JWT_SECRET_KEY por una clave segura y aleatoria.
Installation
Se han agregado las siguientes dependencias a requirements.txt:
PyJWT # Para crear y verificar JWT tokens
passlib[bcrypt] # Para hashing seguro de contraseñas
python-multipart # Para manejo de formularios en login
Para instalar las dependencias:
pip install -r requirements.txt
API Endpoints
1. Crear Usuario (Registro)
POST /api/v1/users/
Crea un nuevo usuario. La contraseña es opcional; si no se proporciona, se usa "passwd123" por defecto.
Request Body
{
"nombre": "Juan",
"apellido": "Pérez",
"email": "juan@example.com",
"contraseña": "miContraseñaSegura123", // Opcional
"fecha_nacimiento": "1990-01-15T00:00:00",
"url_foto_perfil": "https://example.com/foto.jpg", // Opcional
"biografia": "Soy reportero comunitario" // Opcional
}
Response (202 Accepted)
{
"status": "queued",
"message": "Usuario enviado a cola para procesamiento",
"email": "juan@example.com"
}
Casos de Error
- 400 Bad Request: Validación fallida (email inválido, campos vacíos)
- 409 Conflict: Email ya está registrado
2. Login de Usuario (Autenticación)
POST /api/v1/users/login
Autentica un usuario y retorna un JWT token.
Request Body
{
"email": "juan@example.com",
"contraseña": "miContraseñaSegura123"
}
Response (200 OK)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user_id": 1,
"email": "juan@example.com"
}
Casos de Error
- 401 Unauthorized: Email o contraseña incorrectos
JWT Token Usage
Token Structure
Cada token JWT contiene el siguiente payload:
{
"user_id": 1,
"email": "juan@example.com",
"exp": 1704067200, // Unix timestamp de expiración
"iat": 1703980800 // Unix timestamp de creación
}
Authorization Header
Para usar el token en requests a endpoints protegidos:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Example cURL
curl -X POST "http://localhost:8000/api/v1/users/login" \
-H "Content-Type: application/json" \
-d '{
"email": "juan@example.com",
"contraseña": "miContraseñaSegura123"
}'
# Respuesta:
# {
# "access_token": "eyJhbGc...",
# "token_type": "bearer",
# "user_id": 1,
# "email": "juan@example.com"
# }
Password Security
Password Hashing
Las contraseñas se hashean usando bcrypt antes de guardarse en la base de datos:
- Usuario envía contraseña en texto plano
- Se hashea con bcrypt (rounds=12 por defecto)
- Se almacena solo el hash en la base de datos
- Durante login, se verifica comparando el hash
Default Password
Si un usuario no especifica contraseña durante el registro:
Contraseña por defecto: "passwd123"
Hash bcrypt: $2b$12$R9h7cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ee3GzxQ2n8/N7kDi
Database Schema
El modelo de usuario se ha actualizado con:
ALTER TABLE usuarios ADD COLUMN contraseña_hash VARCHAR(255) NOT NULL DEFAULT '$2b$12$R9h7cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ee3GzxQ2n8/N7kDi';
Campos relevantes:
| Campo | Tipo | Descripción |
|---|---|---|
user_id |
INT | Identificador único (PK) |
email |
VARCHAR(255) | Email único |
contraseña_hash |
VARCHAR(255) | Hash bcrypt de la contraseña |
fecha_creacion |
DATETIME | Fecha de registro |
Services Architecture
AuthService (src/infrastructure/api/users/auth_service.py)
Responsable de:
- hash_password() - Hashea contraseñas con bcrypt
- verify_password() - Verifica contraseña contra hash
- create_access_token() - Genera JWT tokens
- verify_token() - Valida y decodifica tokens
- get_default_password_hash() - Retorna hash de "passwd123"
Example Usage
from infrastructure.api.users.auth_service import auth_service
# Hashear contraseña
hashed = auth_service.hash_password("miContraseña")
# Verificar contraseña
is_valid = auth_service.verify_password("miContraseña", hashed)
# Crear token
token = auth_service.create_access_token(user_id=1, email="user@example.com")
# Verificar token
payload = auth_service.verify_token(token)
# payload = {"user_id": 1, "email": "user@example.com", "exp": ..., "iat": ...}
RabbitMQ Message Format
El mensaje de creación de usuario ahora incluye el hash de contraseña:
@dataclass
class UserMessage:
event_type: UserEventType
user_id: Optional[int] = None
nombre: Optional[str] = None
apellido: Optional[str] = None
email: Optional[str] = None
contraseña_hash: Optional[str] = None # ✨ Nuevo
fecha_nacimiento: Optional[str] = None
fecha_creacion: Optional[str] = None
# ... campos adicionales
Example Workflow
1. Registrar usuario
curl -X POST "http://localhost:8000/api/v1/users/" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Carlos",
"apellido": "López",
"email": "carlos@example.com",
"contraseña": "MiPass@2024",
"fecha_nacimiento": "1995-06-20T00:00:00"
}'
2. Login
curl -X POST "http://localhost:8000/api/v1/users/login" \
-H "Content-Type: application/json" \
-d '{
"email": "carlos@example.com",
"contraseña": "MiPass@2024"
}'
# Response:
# {
# "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "token_type": "bearer",
# "user_id": 1,
# "email": "carlos@example.com"
# }
3. Usar token
curl -X GET "http://localhost:8000/api/v1/users/1" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Security Best Practices
✅ En Desarrollo
- Se proporciona
JWT_SECRET_KEYpor defecto para facilitar testing - Cambiar en
.envsi es necesario
✅ En Producción
-
Cambiar
JWT_SECRET_KEY# Generar clave aleatoria python -c "import secrets; print(secrets.token_urlsafe(32))" -
Usar HTTPS
- Los tokens deben viajar solo por HTTPS
-
Secrets Management
- Usar servicios como AWS Secrets Manager, HashiCorp Vault
- No guardar secretos en código
-
Token Rotation
- Considerar refresh tokens con corta expiración
- Implementar revocation list (blacklist)
-
CORS Configuration
- Configurar CORS para permitir solo dominios autorizados
Troubleshooting
Error: "Token expired"
El token tiene más de 24 horas (configurable). Hacer login nuevamente.
Error: "Email o contraseña incorrectos"
Verificar:
- El email existe en la base de datos
- La contraseña es correcta
- El usuario ha sido procesado por el consumer (status "queued" → guardado)
Error: "Secret key not found"
Asegurar que JWT_SECRET_KEY está configurado en:
.envfile- Variable de entorno del sistema
- O usando el default en
core/config.py
Future Enhancements
- Refresh tokens
- Token blacklist para logout
- Multi-factor authentication (MFA)
- OAuth2 integration
- Rate limiting en endpoint de login
- Account lockout después de N intentos fallidos
References
- JWT.io - JWT documentation
- PyJWT Documentation
- Passlib Documentation
- FastAPI Security
Última actualización: 26 de abril de 2026