La autenticación de dos factores (2FA) añade una capa adicional de seguridad más allá de la simple contraseña, combinando «algo que sabes» (contraseña) con «algo que tienes» (código temporal de una app autenticadora). En aplicaciones Django modernas, especialmente aquellas con APIs REST y JWT, implementar 2FA no es solo una buena práctica, sino una necesidad para cumplir con estándares de seguridad como GDPR, HIPAA o PCI-DSS.
Imagina un escenario donde un atacante obtiene la contraseña de un usuario: sin 2FA, tendría acceso completo. Con 2FA implementado correctamente, necesita también el dispositivo del usuario para generar códigos OTP (One-Time Password). Este artículo te guiará desde la teoría hasta una implementación production-ready con Django OTP, JWT personalizado y permisos granulares.
Existen tres categorías principales de factores de autenticación: conocimiento (contraseñas, PIN), posesión (tokens, apps móviles) e inherencia (biometría). Django OTP se enfoca en el segundo factor mediante TOTP (Time-based One-Time Password), el estándar utilizado por Google Authenticator y Authy.
La diferencia clave entre HOTP y TOTP: HOTP usa un contador incremental, mientras que TOTP se basa en el tiempo Unix actual. TOTP es más seguro contra ataques de repetición y sincronización, renovando códigos cada 30 segundos.
| Algoritmo | Base | Ventana Temporal | Seguridad | Uso Típico |
|---|---|---|---|---|
| HOTP | Contador | Indefinida | Media | Tokens hardware |
| TOTP | Tiempo | 30 segundos | Alta | Apps móviles |
| U2F | Hardware | Permanente | Muy Alta | YubiKey |
Comienza creando un entorno virtual limpio e instala las dependencias esenciales. Django OTP requiere paquetes específicos para TOTP y generación de QR:
pip install django-otp djangorestframework-simplejwt qrcode[pil]
En settings.py, registra la app y configura middleware:
INSTALLED_APPS = [ 'django_otp', 'django_otp.plugins.otp_totp', 'rest_framework', 'accounts', # Tu app de usuarios]MIDDLEWARE = [ 'django_otp.middleware.OTPMiddleware', # Para sesiones]
Ejecuta las migraciones para crear los modelos de dispositivos OTP. Django OTP almacena automáticamente los dispositivos por usuario en la base de datos, permitiendo múltiples métodos (TOTP, SMS, etc.) por cuenta.
Crea dos vistas principales: una para generar el dispositivo TOTP (con QR) y otra para verificar códigos. Usa APIView con autenticación JWT previa para la creación.
from django_otp.plugins.otp_totp.models import TOTPDevicefrom django_otp.plugins.otp_totp.models import generate_keyfrom django.contrib.auth import get_user_modeldef get_or_create_totp_device(user): devices = user.otp_devices.filter(confirmed=False) if devices.exists(): return devices.first() key = generate_key() device = TOTPDevice.objects.create( user=user, name='default', key=key, confirmed=False ) return devicedef generate_qr_url(device): return device.config_url
Esta función evita duplicados y mantiene dispositivos no confirmados hasta la primera verificación exitosa, previniendo bloqueos accidentales.
from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.permissions import IsAuthenticatedfrom rest_framework import statusfrom django_otp import devices_for_userclass TOTPCreateView(APIView): permission_classes = [IsAuthenticated] def get(self, request): device = get_or_create_totp_device(request.user) qr_url = generate_qr_url(device) return Response({ 'device_id': device.id, 'qr_url': qr_url, 'secret': device.key # Solo en desarrollo })class TOTPVerifyView(APIView): permission_classes = [IsAuthenticated] def post(self, request): device = get_or_create_totp_device(request.user) token = request.data.get('token') if device.verify_token(token): device.confirmed = True device.save() return Response({'verified': True}) return Response({'verified': False}, status=400)
El desafío principal en APIs REST es adaptar Django OTP (diseñado para sesiones) a JWT stateless. La solución: extender la payload del JWT con otp_device_id y crear permisos que validen verificación OTP.
En authentication.py, crea un Token Manager personalizado:
from rest_framework_simplejwt.tokens import RefreshTokenfrom rest_framework_simplejwt.settings import api_settingsclass OTPSecureToken(RefreshToken): @classmethod def for_user(cls, user, otp_device_id=None): token = super().for_user(user) if otp_device_id: token['otp_device_id'] = otp_device_id return token
from rest_framework.permissions import BasePermissionfrom rest_framework_simplejwt.authentication import JWTAuthenticationclass IsOTPVerified(BasePermission): def has_permission(self, request, view): auth = JWTAuthentication() try: # Extraer JWT y validar payload validated_token = auth.get_validated_token(request.headers['Authorization'].split()[-1]) otp_device_id = validated_token['otp_device_id'] # Verificar que el dispositivo existe y está confirmado device = TOTPDevice.objects.get(id=otp_device_id, confirmed=True, user=request.user) return True except: return False
Aplica este permiso selectivamente: permission_classes = [IsAuthenticated, IsOTPVerified] solo en endpoints sensibles como /profile o /admin.
otp_device_id# Ejemplo de flujo en frontendconst loginFlow = async () => { // 1. Login normal const jwt1 = await login(username, password); // 2. Si requiere 2FA const { qr_url } = await api.get('/totp/create/', { headers: { Authorization: `Bearer ${jwt1}` } }); const code = await getUserTOTP(qr_url); // Google Authenticator // 3. Verificación final const jwt2 = await api.post('/totp/verify/', { token: code }, { headers: { Authorization: `Bearer ${jwt1}` } }); localStorage.setItem('token', jwt2.access); // Usar solo JWT2};
Para testing local, instala qrcode y genera imágenes QR directamente:
import qrcodefrom django.http import HttpResponsedef qr_view(request): device = get_or_create_totp_device(request.user) qr = qrcode.make(device.config_url) return HttpResponse(qr.getvalue(), content_type='image/png')
Códigos de prueba: La clave secreta ONWNOQ3JER4RQ2DJ3AYTFMAP4DQ7BIRT genera 109674 en el momento de ejemplo. Usa apps como Google Authenticator o 1Password para validar.
Para aplicaciones con alto tráfico, considera rate limiting en endpoints TOTP y backups de seeds en base64 seguro. Almacena semillas encriptadas y permite múltiples dispositivos por usuario.
Implementar 2FA en Django es como añadir una segunda cerradura a tu puerta digital. Primero, el usuario ingresa su contraseña normal. Si tiene 2FA activado, recibe un código en su celular que cambia cada 30 segundos. Solo combinando ambos accede. Este doble candado hace casi imposible que hackers entren aunque roben tu contraseña.
La ventaja principal es la simplicidad para usuarios finales: escanean un QR una sola vez y luego solo abren su app de códigos. Tu aplicación se vuelve mucho más segura sin complicar la experiencia del usuario, ideal para startups que quieren impresionar inversores con seguridad enterprise-level.
Esta implementación resuelve el holy grail de 2FA en Django REST: mantener stateless JWT mientras se integra perfectamente con Django OTP’s session-based design. La clave está en el otp_device_id en payload + permisos custom, permitiendo granularidad fina: algunas APIs solo requieren login, otras login+2FA.
Próximos pasos recomendados: Integra WebAuthn para FIDO2/U2F (passwordless), implementa backup codes con django-two-factor-auth, y considera Redis para rate limiting distribuido. Para microservicios, propaga el otp_device_id via headers internos autenticados.
Soluciones personalizadas en desarrollo web, enfocadas en backend y tecnología Django. Transformamos ideas en aplicaciones exitosas con experiencia y dedicación.