Files
VoxPopuli/JWT_AUTHENTICATION.md
2026-04-26 16:24:29 -06:00

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:

  1. Usuario envía contraseña en texto plano
  2. Se hashea con bcrypt (rounds=12 por defecto)
  3. Se almacena solo el hash en la base de datos
  4. 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_KEY por defecto para facilitar testing
  • Cambiar en .env si es necesario

En Producción

  1. Cambiar JWT_SECRET_KEY

    # Generar clave aleatoria
    python -c "import secrets; print(secrets.token_urlsafe(32))"
    
  2. Usar HTTPS

    • Los tokens deben viajar solo por HTTPS
  3. Secrets Management

    • Usar servicios como AWS Secrets Manager, HashiCorp Vault
    • No guardar secretos en código
  4. Token Rotation

    • Considerar refresh tokens con corta expiración
    • Implementar revocation list (blacklist)
  5. 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:

  1. .env file
  2. Variable de entorno del sistema
  3. 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


Última actualización: 26 de abril de 2026