15. Master Class: Autenticación, Seguridad y Email
Bienvenido a la inmersión profunda. En esta sección no solo configuraremos un login; deconstruiremos el sistema de autenticación de Django, implementaremos estándares de seguridad nivel bancario, configuraremos infraestructura SMTP real (Gmail, SendGrid, AWS) y exploraremos el futuro de la identidad digital.
Contenido del Módulo
1. Anatomía de django.contrib.auth
Muchos desarrolladores usan authenticate() y login() como cajas negras. Para escalar, necesitas entender qué ocurre bajo el capó.
El Ciclo de Vida de una Petición Autenticada
Cuando un usuario inicia sesión, Django no "recuerda" al usuario por arte de magia. Utiliza un sistema basado en Sesiones y Middleware.
El rol del Middleware
En tu settings.py, el orden importa:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', # 1. Recupera la sesión de la cookie
'django.contrib.auth.middleware.AuthenticationMiddleware', # 2. Asocia la sesión al Usuario
# ...
]
- SessionMiddleware: Busca una cookie llamada
sessionid. Si existe, busca en la base de datos (tabladjango_session) los datos asociados y los coloca enrequest.session. - AuthenticationMiddleware: Busca dentro de
request.sessionuna clave especial (_auth_user_id). Si la encuentra, busca ese ID en la tabla de usuarios y coloca el objeto usuario enrequest.user.
Si eliminas AuthenticationMiddleware, request.user no existirá.
Backends de Autenticación
Django permite autenticar contra múltiples fuentes: base de datos local, LDAP, OAuth, etc. Esto se controla con AUTHENTICATION_BACKENDS.
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend', # Por defecto (Base de datos)
'guardians.backends.ObjectPermissionBackend', # Librería externa para permisos por objeto
]
Cuando llamas a authenticate(request, username=u, password=p), Django itera sobre esta lista probando cada backend hasta que uno acepte las credenciales.
2. Seguridad y Estándares (Hardening)
⚠️ Advertencia de Seguridad
Nunca almacenes contraseñas en texto plano. Jamás. Django se encarga de esto, pero debes configurar los algoritmos correctamente.
Hashing de Contraseñas: PBKDF2 vs Argon2
Por defecto, Django usa PBKDF2 con SHA256 y miles de iteraciones. Es seguro y estándar NIST. Sin embargo, para aplicaciones de alta seguridad, Argon2 es el ganador actual de la competencia de hashing de contraseñas (PHC).
Cómo activar Argon2 en Django:
- Instala la librería C bindings:
pip install django[argon2] - Configura en
settings.py:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher', # Primero en la lista = Default
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]
Esto actualizará automáticamente las contraseñas de los usuarios a Argon2 la próxima vez que inicien sesión.
Protección de Sesiones
Para evitar el Session Hijacking (robo de cookies) y ataques XSS/CSRF, tu configuración de producción (production.py) debe incluir:
# Solo enviar cookies a través de HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Evitar que JavaScript acceda a la cookie (Protección XSS)
SESSION_COOKIE_HTTPONLY = True
# Evitar envío de cookies en iframes o sitios externos (Protección CSRF/Clickjacking)
SESSION_COOKIE_SAMESITE = 'Lax' # o 'Strict' para máxima seguridad
3. Infraestructura de Email y SMTP
El envío de correos es el talón de Aquiles de muchos despliegues. Aquí explicamos cómo pasar de un simple console.EmailBackend a una infraestructura robusta de entrega.
El protocolo SMTP en Django
Django abstrae el envío de correos mediante EMAIL_BACKEND. Esto permite cambiar la implementación sin tocar una sola línea de tu código de vistas.
Entorno de Desarrollo
Usa la consola o un servidor dummy.
django.core.mail.backends.console.EmailBackend
Entorno de Producción
Usa SMTP real o APIs transaccionales.
django.core.mail.backends.smtp.EmailBackend
Guía de Configuración por Proveedor
A. Gmail (Para pruebas o proyectos pequeños)
Google ya no permite usar tu contraseña normal. Debes activar "Verificación en 2 pasos" y generar una Contraseña de Aplicación.
# settings/production.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'tu_correo@gmail.com'
EMAIL_HOST_PASSWORD = config('GMAIL_APP_PASSWORD') # Usa python-decouple
B. SendGrid / Mailgun (Estándar de Industria)
Estos servicios garantizan entregabilidad (evitan que caigas en SPAM). Ofrecen credenciales SMTP dedicadas.
# settings/production.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'apikey' # SendGrid suele usar este usuario literal
EMAIL_HOST_PASSWORD = config('SENDGRID_API_KEY')
C. Amazon SES (Alta Escala y Costo Eficiente)
AWS SES es extremadamente barato pero difícil de configurar (requiere validar dominio y salir del sandbox). Se recomienda usar django-ses o django-anymail.
# Requiere: pip install django-ses
EMAIL_BACKEND = 'django_ses.SESBackend'
AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY')
AWS_SES_REGION_NAME = 'us-east-1'
AWS_SES_REGION_ENDPOINT = 'email.us-east-1.amazonaws.com'
Rendimiento: Envío Asíncrono (Celery)
Nunca envíes correos en el hilo principal de la petición web. El SMTP es lento (1-3 segundos). Si envías un correo durante el registro, el usuario esperará 3 segundos viendo el navegador "pensando".
La solución profesional es usar colas de tareas (Celery + Redis):
# tasks.py
@shared_task
def enviar_bienvenida_async(user_id):
user = User.objects.get(id=user_id)
send_mail(...)
4. Alternativas Modernas de Autenticación
¿Cuándo usar sesiones y cuándo usar tokens? Esta es la duda existencial del backend moderno.
| Estrategia | Tecnología | Caso de Uso Ideal |
|---|---|---|
| Session Auth | Cookies HTTPOnly | Aplicaciones Web tradicionales (Server Side Rendering), Admin de Django. Más seguro contra XSS. |
| Token Auth | Header Authorization | APIs simples, Clientes móviles nativos, IoT. |
| JWT (JSON Web Token) | SimpleJWT / Auth0 | Microservicios, SPAs (React/Vue) donde se requiere stateless total. |
| OAuth2 / OIDC | django-allauth | "Entrar con Google/Facebook" o federación de identidad corporativa. |
Ejemplo de JWT con Django Rest Framework
Si construyes una API para React/Flutter, usarás djangorestframework-simplejwt.
# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
5. Implementación Práctica en el Proyecto
Ahora aplicaremos todo este conocimiento para configurar un sistema robusto de Login y Recuperación de Contraseña en nuestro proyecto educativo.
Paso 1: Configuración Base
En src/curso_django/settings/base.py, asegúrate de tener las URLs de redirección:
LOGIN_REDIRECT_URL = 'curso_list_cbv'
LOGOUT_REDIRECT_URL = 'curso_list_cbv'
Paso 2: Ruteo de Autenticación
En src/curso_django/urls.py, delegamos en las vistas built-in de Django:
from django.urls import path, include
urlpatterns = [
# ... otras urls
path("accounts/", include("django.contrib.auth.urls")),
]
Paso 3: Email para Desarrollo
Para probar el reset de password localmente sin internet ni servidores SMTP, usaremos la consola.
En src/curso_django/settings/local.py:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
Paso 4: Vistas y Plantillas
Django buscará por defecto en registration/. Crearemos las plantillas usando Tailwind CSS para mantener la estética.
Estructura de archivos en src/templates/registration/:
login.html: Formulario de entrada.password_reset_form.html: Solicita el email.password_reset_done.html: Aviso de "Correo enviado".password_reset_confirm.html: Formulario para ingresar nueva clave (link desde el email).password_reset_complete.html: Confirmación final.
Validación del Flujo
Cómo probar esto como un profesional:
- Abre una pestaña de incógnito.
- Navega a
/accounts/login/y haz click en "¿Olvidaste tu contraseña?". - Ingresa el correo de tu superusuario.
- Observa tu terminal donde corre
runserver. Verás el cuerpo del email crudo. - Copia el enlace que luce como
/accounts/reset/Mq/5lm-9a...y ábrelo en el navegador. - Cambia la contraseña y verifica que puedes iniciar sesión con la nueva.