Autenticación JWT11 temas

Autenticación JWT

JWT (JSON Web Token) es un estándar para transmitir información entre partes como un objeto JSON firmado y verificable. En APIs REST sirve para autenticación stateless: el servidor no guarda sesión, el token lleva la identidad.

Trade-off:

No se puede revocar un access token antes de su expiración. Por eso existen refresh tokens + blacklist (ver guía avanzada).


Anatomía de un JWT

Diagrama: anatomía de un JWT

El token no está encriptado (cualquiera puede decodificar el payload con base64). La firma garantiza que no fue modificado. No pongas secrets (passwords, claves API) en el payload.


Flujo básico Access + Refresh

Diagrama: flujo básico de access token y refresh token

Instalación y configuración mínima

pip install djangorestframework-simplejwt
# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
}

from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'AUTH_HEADER_TYPES': ('Bearer',),
}

JWTAuthentication lee el header Authorization: Bearer <token>, valida firma y expiración, y setea request.user.


URLs de autenticación

# urls.py
from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('auth/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
  • POST /auth/token/ con {username, password} → {access, refresh}
  • POST /auth/refresh/ con {refresh} → {access}

Protegiendo endpoints

# views.py
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response


class PerfilView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({
            "user_id": request.user.id,
            "username": request.user.username,
            "email": request.user.email,
        })

Con IsAuthenticated, cualquier request sin token válido → 401 Unauthorized.


Probando el flujo completo

# 1. Login
curl -X POST http://localhost:8000/auth/token/ \
  -H "Content-Type: application/json" \
  -d '{"username": "tu_user", "password": "tu_pass"}'
# {"access": "eyJhbGciOi...", "refresh": "eyJhbGciOi..."}

# 2. Request autenticado
curl -H "Authorization: Bearer eyJhbGciOi..." \
  http://localhost:8000/api/perfil/

# 3. Refresh (cuando access expira)
curl -X POST http://localhost:8000/auth/refresh/ \
  -H "Content-Type: application/json" \
  -d '{"refresh": "eyJhbGciOi..."}'
# {"access": "eyJhbGciOi...nuevo..."}

Errores comunes

Error Causa Solución
401 "Token is invalid or expired" Access expirado o firma inválida Usar /auth/refresh/ con refresh válido
401 "Authentication credentials not provided" Falta header Authorization Enviar Authorization: Bearer <access>
400 "Token is blacklisted" Refresh en blacklist (logout) Re-loguear usuario
KeyError 'user_id' en custom serializer SimpleJWT usa user_id por defecto Verificar que tu User model tenga id o pk

Buenas prácticas básicas

Práctica Por qué
Access token corto (15 min) Limita exposición si token es robado
Refresh token largo (7-30 días) UX: usuario no re-loguea constante
HTTPS obligatorio en prod Tokens viajan en headers, sin TLS se interceptan
SECRET_KEY fuerte y rotada Firma HS256 usa esta clave; si se filtra, firman tokens falsos