Django Guía

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.

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
    # ...
]
  1. SessionMiddleware: Busca una cookie llamada sessionid. Si existe, busca en la base de datos (tabla django_session) los datos asociados y los coloca en request.session.
  2. AuthenticationMiddleware: Busca dentro de request.session una clave especial (_auth_user_id). Si la encuentra, busca ese ID en la tabla de usuarios y coloca el objeto usuario en request.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:

  1. Instala la librería C bindings: pip install django[argon2]
  2. 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')
Pro Tip: Para alto volumen, no uses SMTP. Usa la API HTTP directa mediante librerías como django-anymail, que es más rápida y robusta.

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:
  1. Abre una pestaña de incógnito.
  2. Navega a /accounts/login/ y haz click en "¿Olvidaste tu contraseña?".
  3. Ingresa el correo de tu superusuario.
  4. Observa tu terminal donde corre runserver. Verás el cuerpo del email crudo.
  5. Copia el enlace que luce como /accounts/reset/Mq/5lm-9a... y ábrelo en el navegador.
  6. Cambia la contraseña y verifica que puedes iniciar sesión con la nueva.