Un viaje por el código fuente de DRF: Por qué solo usaba autenticadores personalizados y el redescubrimiento de los autenticadores 'integrados'
1. Amo DRF, pero no confiaba en sus autenticadores integrados.
Django y DRF (Django Rest Framework) han sido compañeros constantes en mi carrera como desarrollador. Sin embargo, cuando se trataba de autenticación, siempre tuve una postura un tanto obstinada. Mi preferencia se inclinaba hacia métodos como JWT, API Key u OAuth2, ya que son el estándar en los entornos de aplicaciones modernas.
Curiosamente, al revisar la documentación oficial de DRF, notaba que las clases de autenticación que parecían más 'antiguas', como Basic, Session, Token, Remote, ocupaban un lugar central, en lugar de los métodos modernos que yo solía usar. A menudo me preguntaba: '¿Por qué un framework tan capaz como DRF incluiría esto de forma nativa?'. Por eso, siempre optaba por heredar de BaseAuthentication y crear mis propios autenticadores personalizados.
2. ¿Por qué volver a revisar el código fuente de los autenticadores 'integrados'?
Mientras desarrollaba mis autenticadores personalizados, de repente me surgió una pregunta:
"¿Mi autenticador realmente se integra a la perfección con la filosofía de Django y DRF?"
Me preocupaba si, más allá de simplemente asignar el usuario a request.user, estaba perdiendo la 'naturalidad' del esquema de autenticación diseñado por DRF. Por eso, a partir de hoy, me propongo analizar en detalle los cuatro autenticadores integrados de DRF a lo largo de varias entregas.
- BasicAuthentication (Autenticación HTTP básica)
- SessionAuthentication (Uso de sesiones de Django)
- TokenAuthentication (Autenticación por token simple)
- RemoteUserAuthentication (Integración de autenticación externa)
3. Descubriendo la 'filosofía' detrás de lo 'incómodo'
Para ser sincero, usar BasicAuthentication tal cual en un servicio moderno resulta incómodo. Parece vulnerable y la estructura de enviar el usuario y la contraseña en cada solicitud genera inseguridad. Sin embargo, al analizar su código fuente, se revela un diseño muy sofisticado sobre 'cómo comunicarse con el cliente en caso de fallo de autenticación'.
Por ejemplo, la diferencia entre simplemente lanzar un 403 Forbidden y guiar al cliente hacia un 401 Unauthorized (el estándar) a través de authenticate_header. Estos detalles son precisamente la clave para construir clases que sean verdaderamente "al estilo Django".
4. El objetivo de esta serie: Hacer que lo personalizado sea 'natural'
Al final de esta serie, lo que busco no es solo conocimiento.
- Inspiración: Aprender la filosofía de diseño detrás de la estructura de los autenticadores integrados.
- Adaptación: Trasladar esa filosofía a mis autenticadores personalizados de JWT u OAuth2, que son mis favoritos.
- Armonía: Lograr que mis autenticadores personalizados funcionen de forma tan natural dentro del sistema de autenticación de DRF como si siempre hubieran estado ahí.
Es el primer paso para ir más allá de ser un desarrollador que simplemente implementa funcionalidades, para convertirme en uno capaz de integrar la filosofía del framework en mi código. La serie de análisis del código fuente de los autenticadores de DRF comienza ahora.

5. Echando un vistazo al código fuente: BasicAuthentication
La introducción ha sido larga. Ahora, abramos el corazón de BasicAuthentication.
Al abrir rest_framework/authentication.py de DRF, nos encontramos con la esencia de esta clase. Es más concisa de lo que uno podría esperar, pero contiene reglas sólidas.
class BasicAuthentication(BaseAuthentication):
"""
HTTP Basic authentication against username/password.
"""
www_authenticate_realm = 'api'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'basic':
return None
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
try:
auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
except UnicodeDecodeError:
auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
userid, password = auth_decoded.split(':', 1)
except (TypeError, ValueError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(userid, password, request)
def authenticate_credentials(self, userid, password, request=None):
credentials = {
get_user_model().USERNAME_FIELD: userid,
'password': password
}
user = authenticate(request=request, **credentials)
if user is None:
raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
if not user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (user, None)
def authenticate_header(self, request):
return 'Basic realm="%s"' % self.www_authenticate_realm
¿Por qué precisamente en el encabezado (Header)?
Normalmente, al enviar datos, pensamos primero en request.POST o request.data. Sin embargo, la información de autenticación se suele incluir en el encabezado. ¿Por qué?
Porque la autenticación debe responder de manera flexible a todos los métodos HTTP (GET, POST, PUT, DELETE, etc.). Las solicitudes GET no tienen cuerpo, y las DELETE a menudo también lo tienen vacío. Si tuviéramos que poner la información de autenticación en el cuerpo, nos veríamos obligados a usar POST incluso para consultas, lo que resultaría en un diseño extraño. Incluir la autenticación en el encabezado es como decir: "No importa qué puerta abras, ten tu identificación a mano".
La cruda realidad: Vulnerabilidades de seguridad
La principal desventaja de este método es Base64. No es encriptación, sino una simple 'codificación'. Cualquiera puede decodificar tu usuario y contraseña en un segundo usando un sitio web de decodificación.
Conclusión: Su uso está completamente prohibido en entornos HTTP. Solo debe emplearse dentro de un túnel seguro (HTTPS), y para propósitos muy limitados.
Primera inspiración: Seguridad rentable para comunicación máquina a máquina (M2M)
A pesar de todo, hay momentos en que este antiguo autenticador resulta atractivo. Es el caso de la comunicación entre servidores. Cuando construir un sistema HMAC o de claves API complejas resulta excesivo en recursos, pero enviar datos en texto plano es inseguro, podemos 'retorcer' ligeramente esta estructura.
- Cifrado personalizado: En lugar de Base64, se envía contenido binario cifrado con una clave simétrica conocida solo por ambos servidores.
- Implementación sencilla: Permite una comunicación rápida y segura con solo la forma
Basic <Encrypted_Data>, sin necesidad de un handshake complejo. Se convierte en un canal eficiente para intercambiar "contraseñas que solo nosotros conocemos".
Segunda inspiración: El engranaje perdido authenticate_header()
El método que merece mayor atención es authenticate_header(). Por mucho que busquemos en el código fuente, no hay un lugar que lo llame directamente. Sin embargo, este método es la clave fundamental para generar un '401 Unauthorized' dentro del manejador de excepciones de DRF.
Al crear autenticadores personalizados, un error común es omitir este método. El hecho es que, si se omite, no afecta la acción de autenticación en sí. No obstante, es crucial comparar cómo esta pequeña diferencia puede ensuciar el código a nivel de la vista.
Caso 1. Cuando se omite authenticate_header (método manual)
Si este método no existe, DRF asume que "no puede proporcionar una guía sobre cómo autenticarse" y, en caso de fallo de autenticación, lanza un 403 Forbidden en lugar de un 401. Para evitar esto, se necesita un trabajo 'extra' en la vista, como se muestra a continuación:
# ❌ authenticate_header가 없는 경우
class MyPostAPIView(APIView):
# IsAuthenticated를 쓰면 무조건 403이 나가므로, 401을 위해 일단 열어줍니다.
permission_classes = [AllowAny]
def post(self, request):
# 뷰 내부에서 일일이 인증 여부를 확인하고 401을 수동으로 반환해야 합니다.
if not request.user or not request.user.is_authenticated:
return Response(
{"detail": "로그인이 필요합니다."},
status=status.HTTP_401_UNAUTHORIZED
)
# ... 실제 로직 시작 ...
Caso 2. Cuando se implementa authenticate_header (implementación de la filosofía de DRF)
Por otro lado, si se incluye una única línea de indicación (authenticate_header) en el autenticador personalizado, el vasto esquema de autenticación de DRF lo reconocerá y ensamblará automáticamente la respuesta 401.
# ✅ authenticate_header가 있는 경우
class MyPostAPIView(APIView):
# 이제 DRF가 알아서 401을 던져줄 것이므로, 선언적으로 권한을 제어할 수 있습니다.
permission_classes = [IsAuthenticated]
def post(self, request):
# 뷰는 오직 '비즈니스 로직'에만 집중합니다.
# 인증 실패 처리는 이미 윗단(인증기의 authenticate_header메서드)에서 끝났기 때문입니다.
...
Esto es la verdadera estética "al estilo Django". En lugar de dispersar código fragmentado en la vista, se siente como encajar perfectamente nuestro código entre los engranajes diseñados por el framework. Con este único método, podemos separar claramente los roles de 'autenticación' y 'autorización', manteniendo la vista mucho más ligera.
El verdadero encanto de DRF: La elegante concesión (la estética de None)
Finalmente, volvamos a esta parte del código fuente.
if not auth or auth[0].lower() != b'basic':
return None
En lugar de lanzar un error cuando el formato no coincide, devuelve None y se retira discretamente. Este pequeño detalle es lo que otorga flexibilidad a DRF. Gracias a ello, podemos incluir múltiples autenticadores en la lista AUTHENTICATION_CLASSES en orden. ¿No se percibe la huella de desarrolladores que pensaron: "Ah, ¿no es mi método? ¡Entonces que lo intente el siguiente autenticador!"?
Conclusión
Aunque BasicAuthentication parezca anticuado, su estructura es el fundamento de los sistemas de autenticación modernos. Especialmente su manejo de excepciones y el soporte para autenticación múltiple son activos que debemos incorporar al crear nuestros autenticadores personalizados.
Hemos superado la primera etapa. En la próxima publicación, exploraremos a fondo SessionAuthentication, un método que nos resulta familiar pero que sigue siendo un misterio.
¡Estén atentos a "Lo que no sabías sobre la autenticación por sesión, incluso si la usas"!
No hay comentarios.