Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 17 de 30

Autenticación con tokens y JWT

El token que se guarda en claro y no caduca, el JWT que consulta la base de datos igual que él, y la palabra que hay que escribir para que te dejen entrar.

⏱ 17 min de lectura

La lección 7 vio la autenticación desde el lado de quien consume una API. Ahora toca escribirla, y DRF trae dos caminos: su sistema de tokens, que viene incluido, y JWT, que necesita un paquete.

El argumento habitual para elegir entre los dos es que el JWT no consulta la base de datos. Medido aquí, los dos consultan.

Los esquemas que trae DRF#

python
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}
EsquemaPara qué
SessionAuthenticationLa cookie de Django. Para tu propio sitio web
BasicAuthenticationUsuario y contraseña en cada petición. Para pruebas
TokenAuthenticationUn token por usuario en la base de datos
RemoteUserAuthenticationCuando autentica un servidor delante

El orden importa: DRF prueba una tras otra y se queda con la primera que devuelva un usuario. Y si ninguna lo hace, el usuario es anónimo y decide el permiso.

Un detalle que hay que conocer: la de sesión comprueba el CSRF. Es correcto —una cookie viaja sola y hay que protegerla, que es la lección 42 del módulo 4— y es la causa del 403 misterioso al probar desde el navegador estando identificado. La de token no lo comprueba, porque una cabecera no la manda el navegador por su cuenta.

El token de DRF#

Se activa con una aplicación, una migración y una ruta:

python
INSTALLED_APPS = ['rest_framework.authtoken', ...]
python
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [path('api/token/', obtain_auth_token)]

Y ya funciona:

Salida en pantalla
POST /api/token/  {"username": "ana", "password": "clave-larga-123"}
200  {"token": "6cda1889e2aac40d35697a4c944f66c1bd9ffabc"}
Salida en pantalla
GET /api/autores/   con  Authorization: Token 6cda1889...
200

Las tres limitaciones del token de DRF#

Hay que conocerlas antes de elegirlo, porque son de diseño y no se configuran:

Salida en pantalla
clave:    6cda1889e2aac40d35697a4c944f66c1bd9ffabc   (40 hex)
campos:   ['key', 'user', 'created']
clave primaria: key

Uno por usuario. El usuario es único en la tabla, así que una persona no puede tener un token para el móvil y otro para un guion. Cerrar sesión en un sitio la cierra en todos.

No caduca. No hay campo de caducidad ni comprobación. Un token emitido hoy vale dentro de cinco años.

Se guarda en claro:

Salida en pantalla
en la base de datos: ('6cda1889e2aac40d35697a4c944f66c1bd9ffabc', 1)

Ahí está la clave tal cual. Django se toma muchas molestias en guardar las contraseñas con resumen —la lección 32 del módulo 4— y luego guarda estos tokens sin ninguna, aunque den el mismo acceso.

Quien consiga una copia de la base de datos tiene todas las cuentas, sin romper nada. Una copia de seguridad mal guardada, un volcado para depurar, un desarrollador con acceso de lectura: cualquiera de esas cosas es suficiente.

Lo que esto significa en la práctica:

Vale para APIs internas, guiones de la casa, integraciones entre servicios tuyos. Es simple y funciona.

No vale para una aplicación con usuarios reales. Ahí hacen falta tokens que caduquen, que se revoquen y que no estén en claro.

Y hay paquetes que lo arreglan: django-rest-knox da varios tokens por usuario, con caducidad y guardados con resumen, manteniendo la simplicidad.

JWT#

El paquete de referencia:

bash
pip install djangorestframework-simplejwt
python
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}
python
from rest_framework_simplejwt.views import (
    TokenObtainPairView, TokenRefreshView, TokenVerifyView)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view()),
    path('api/token/refrescar/', TokenRefreshView.as_view()),
    path('api/token/verificar/', TokenVerifyView.as_view()),
]

Y devuelve los dos tokens de la lección 7:

Salida en pantalla
POST /api/token/  {"username": "ana", "password": "clave-larga-123"}
200  {"access": "...", "refresh": "..."}

longitud del de acceso: 231 caracteres

cabecera: {"alg": "HS256", "typ": "JWT"}
carga:    {"token_type": "access", "exp": 1790105720, "iat": 1790105420,
           "jti": "7926d06f23d241cdb608c9c5934fe6b5", "user_id": "1"}

el de acceso vive 5 minutos
el de refresco vive 1 día

Cinco minutos de fábrica, que es conservador y correcto. Y fíjate en el jti, un identificador único del token: es lo que permite anotarlo en una lista negra, que es lo único que hace revocable un JWT.

Elegir#

La decisión, sin la parte de moda:

Token de DRFJWTSesión
CaducaNoSíSí
Se revocaAl instanteNo, salvo lista negraAl instante
Varios por usuarioNoSíSí
Guardado en claroSíNo se guardaNo
Consultas por petición11 (por defecto)1
Sirve entre serviciosNoSíNo
CSRFNo aplicaNo aplicaSí

Si tu API la consume tu propio sitio web, en el mismo dominio, la sesión es la respuesta y no hace falta nada más. Es lo que menos código tiene y lo más seguro, porque la cookie puede ser HttpOnly y el JavaScript de la página no puede leerla.

Si es una aplicación móvil o un cliente ajeno, tokens. Y entonces JWT si quieres caducidad y refresco, o Knox si quieres lo mismo con revocación instantánea.

Si tienes varios servicios que tienen que verificar quién eres sin compartir base de datos, JWT es la única de las tres que sirve.

Si es una API interna entre programas tuyos, el token de DRF basta, guardado en el entorno.