Skip to main content

Autenticación JWT

El sistema utiliza JSON Web Tokens (JWT) para autenticación. Cada token contiene información del usuario codificada y firmada, permitiendo autenticación stateless.

Obtener tokens

Para autenticarse, envíe credenciales al endpoint de tokens:
POST /api/token/
Request body:
{
  "email": "[email protected]",
  "password": "contraseña_segura"
}
Response:
{
  "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "user": {
    "id": 5,
    "email": "[email protected]",
    "nombre": "Juan Pérez",
    "rol": "auxiliar"
  }
}
El token access es de corta duración (generalmente 15-60 minutos). El token refresh es de larga duración (generalmente 1-7 días) y se usa para obtener nuevos access tokens.

Validación personalizada

El sistema implementa validación personalizada en CustomTokenObtainPairView:
class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

    def post(self, request, *args, **kwargs):
        email = request.data.get("email")
        password = request.data.get("password")

        # Verificar si el correo existe
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            return Response(
                {"detail": "Correo no encontrado"}, 
                status=status.HTTP_404_NOT_FOUND
            )

        # Autenticar usuario
        user = authenticate(request, email=email, password=password)
        if user is None:
            return Response(
                {"detail": "Contraseña incorrecta"}, 
                status=status.HTTP_401_UNAUTHORIZED
            )

        return super().post(request, *args, **kwargs)
Esto proporciona mensajes de error específicos:
  • 404: El correo no existe en el sistema
  • 401: La contraseña es incorrecta

Refrescar tokens

Cuando el access token expira, use el refresh token para obtener uno nuevo:
POST /api/token/refresh/
Request body:
{
  "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Response:
{
  "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Implemente lógica en su cliente para refrescar automáticamente tokens antes de que expiren. En Angular, use un interceptor HTTP.

Usar tokens en peticiones

Incluya el access token en el header Authorization de todas las peticiones:
curl -X GET http://localhost:8000/api/inventario/ \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

Implementación en frontend

En Angular, configure un interceptor para agregar el token automáticamente:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('access_token');
    
    if (token) {
      req = req.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    
    return next.handle(req);
  }
}

Protección de endpoints

Todos los endpoints (excepto /api/token/) requieren autenticación:
from rest_framework import permissions

class InventarioViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated]
    # ...
Si intenta acceder sin token, recibirá:
{
  "detail": "Authentication credentials were not provided."
}
Si el token expiró:
{
  "detail": "Given token not valid for any token type",
  "code": "token_not_valid",
  "messages": [
    {
      "token_class": "AccessToken",
      "token_type": "access",
      "message": "Token is invalid or expired"
    }
  ]
}

Control de acceso basado en roles

Además de autenticación, el sistema implementa autorización mediante roles. El decorador @permiso_requerido valida permisos:
from usuarios.decorators import permiso_requerido

@api_view(['DELETE'])
@permission_classes([permissions.IsAuthenticated])
@permiso_requerido('crud_activos')
def eliminar_activo(request, placa):
    # Solo usuarios con permiso 'crud_activos' pueden ejecutar esto
    # (Sys Admin en este caso)
    pass
Si un usuario sin permisos intenta acceder:
{
  "detail": "No tiene permiso para realizar esta acción"
}
Vea la lista completa de roles y permisos en Roles de usuario.

Seguridad en producción

IMPORTANTE: Antes de desplegar a producción, configure estas variables en settings.py:

Secret key

Genere una clave secreta única:
import secrets
SECRET_KEY = secrets.token_urlsafe(50)
Nunca comparta esta clave ni la suba a repositorios públicos.

Configuración HTTPS

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True

CORS

Limite los orígenes permitidos:
CORS_ALLOWED_ORIGINS = [
    "https://app.trebol.com",
]

Duración de tokens

Ajuste la duración de tokens según sus necesidades de seguridad:
from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
}
Cuando está habilitado, cada vez que usa un refresh token para obtener un nuevo access token, también recibe un nuevo refresh token. El refresh token anterior se invalida (si BLACKLIST_AFTER_ROTATION es True).Esto mejora la seguridad, especialmente si un refresh token es comprometido.

Mejores prácticas

1

Almacenamiento seguro de tokens

En el frontend, almacene tokens en localStorage o sessionStorage, no en cookies accesibles por JavaScript si no usa httpOnly.
2

Logout del cliente

Al hacer logout, elimine tokens del almacenamiento del cliente:
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
3

Manejo de errores 401

Implemente lógica para redirigir al login cuando reciba errores 401.
4

Renovación automática

Implemente lógica para renovar tokens automáticamente antes de que expiren, evitando interrupciones en la experiencia del usuario.

Solución de problemas

Error: “Token is invalid or expired”

Causa: El access token expiró. Solución: Use el refresh token para obtener un nuevo access token mediante /api/token/refresh/.

Error: “Authentication credentials were not provided”

Causa: No se incluyó el header Authorization o está mal formado. Solución: Asegúrese de incluir Authorization: Bearer <token> en todas las peticiones.

Error: “Correo no encontrado”

Causa: El email no existe en la base de datos. Solución: Verifique el email o registre un nuevo usuario.

Error: “Contraseña incorrecta”

Causa: La contraseña no coincide. Solución: Verifique la contraseña o use la funcionalidad de recuperación de contraseña.

Recursos adicionales

API de autenticación

Documentación completa de endpoints de autenticación

Roles y permisos

Entienda el sistema de roles del sistema

Build docs developers (and LLMs) love