Autenticación Avanzada
JWT (JSON Web Tokens) es el estándar más usado para autenticación en APIs REST. Con djangorestframework-simplejwt se puede implementar un sistema completo con refresh tokens, claims personalizados y revocación de sesiones.
Flujo básico de JWT
Antes de entrar en código, este es el flujo típico de JWT con access y refresh tokens:
El cliente envía credenciales, recibe dos tokens, usa el access para pedir datos, y cuando expira usa el refresh para obtener uno nuevo sin pedir credenciales de nuevo.
Custom Claims
Un claim es un campo con información dentro del token JWT. SimpleJWT incluye estos por defecto:
| Claim | Significado |
|---|---|
token_type |
"access" o "refresh" — qué tipo de token es |
exp |
Fecha de expiración (timestamp Unix) |
iat |
Fecha de emisión (timestamp Unix) |
jti |
ID único del token (para blacklist, etc.) |
user_id |
ID del usuario autenticado |
Además de estos, el frontend recibe access y refresh (los strings del token), más payload con los claims si lo decodificás.
Pero se pueden agregar claims personalizados para evitar consultar la base de datos en cada request. Por ejemplo, si el rol del usuario no cambia seguido, guardalo en el token:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Claims personalizados (se SUMAN a los estándar)
token["email"] = user.email
token["role"] = getattr(user, "role", "user")
token["is_verified"] = user.is_verified
return token
class CustomTokenView(TokenObtainPairView):
serializer_class = CustomTokenSerializer
Esto permite leer role o email directamente del token en el frontend o en middlewares, sin hacer queries adicionales. El token se vuelve autosuficiente para ciertas decisiones.
Logout con Blacklist
Los JWT son stateless por diseño: no se puede invalidar un access token hasta que expire. Pero sí se puede blacklistear el refresh token para que el usuario no pueda renovar la sesión:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError
class LogoutView(APIView):
"""
Invalida el refresh token para cerrar la sesión.
El access token actual expirará naturalmente.
"""
permission_classes = [IsAuthenticated]
def post(self, request):
refresh = request.data.get("refresh")
if not refresh:
return Response(
{"error": "refresh token requerido"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
token = RefreshToken(refresh)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except TokenError:
return Response(
{"error": "Token inválido o expirado"},
status=status.HTTP_400_BAD_REQUEST,
)
Importante: El access token sigue siendo válido hasta su expiración natural (por diseño JWT). La blacklist del refresh token impide renovar la sesión, pero el access actual se mantiene. Si se necesita invalidación inmediata, considerar usar tokens opacos (sesiones de Django) o listas de denegación de access tokens.
Flujo completo con Blacklist
Acá se ve cómo entra la blacklist en el ciclo de vida completo, incluyendo logout:
La diferencia clave: el access token no se invalida al hacer logout. Solo el refresh se blacklistea. Si alguien robó el access token, lo va a poder usar hasta que expire.
URLs
from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
path('auth/token/', CustomTokenView.as_view(), name='token_obtain'),
path('auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('auth/logout/', LogoutView.as_view(), name='logout'),
]
Configuración recomendada
# settings.py
INSTALLED_APPS = [
...
'rest_framework_simplejwt.token_blacklist',
]
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
}
Buenas prácticas
| Práctica | Por qué |
|---|---|
ROTATE_REFRESH_TOKENS: True |
Cada vez que se renueva, el refresh anterior se invalida |
BLACKLIST_AFTER_ROTATION: True |
Evita que refresh tokens viejos sigan siendo válidos |
| Access token corto (15-30 min) | Minimiza la ventana de exposición si un token es robado |
| Refresh token más largo (7-30 días) | Buena experiencia de usuario sin re-login constante |