El JWT_SECRET es una clave secreta que se usa para firmar los tokens JWT. Esta firma garantiza que el token no ha sido modificado. Si alguien intenta alterar el contenido del token, la firma no coincidirá y será rechazado. Nunca compartas este secret y usa uno diferente para cada entorno (desarrollo, producción).
Generar secrets seguros
Usa este comando para generar una clave criptográficamente segura:
Las rutas no incluidas en exclude requieren autenticación automáticamente.
JWTCookieAuth (opcional)
Para usar cookies en lugar de headers Authorization:
from litestar.security.jwt import JWTCookieAuthjwt_cookie_auth = JWTCookieAuth[User]( retrieve_user_handler=retrieve_user_handler, token_secret=os.environ.get("JWT_SECRET", "secret"), default_token_expiration=timedelta(hours=1), exclude=["/login", "/register", "/schema", "/docs"], auth_cookie_httponly=True, # Protección contra XSS auth_cookie_secure=True, # Solo HTTPS auth_cookie_samesite="lax", # Protección CSRF)
Las cookies son más seguras (HttpOnly previene XSS) pero requieren configuración CSRF adicional.
Control de acceso con Guards
Los guards son funciones que verifican permisos antes de ejecutar un handler. Si lanzan una excepción, la request es rechazada.
Guard básico
from litestar.connection import ASGIConnectionfrom litestar.handlers.base import BaseRouteHandlerfrom litestar.exceptions import NotAuthorizedExceptionasync def admin_guard(connection: ASGIConnection, handler: BaseRouteHandler) -> None: if connection.user.role != "admin": raise NotAuthorizedException("Se requiere rol de administrador")
Uso
Aplica guards a endpoints, controladores o globalmente:
# En un endpoint@get("/admin/users", guards=[admin_guard])async def list_users() -> list[User]: ...# En un controlador (aplica a todos los endpoints)class AdminController(Controller): path = "/admin" guards = [admin_guard] ...# Globalmente (aplica a toda la app)app = Litestar(route_handlers=[...], guards=[admin_guard])
Guards con parámetros dinámicos
Usa handler.opt para pasar datos al guard:
async def permission_guard(connection: ASGIConnection, handler: BaseRouteHandler) -> None: required = handler.opt.get("permission") if required and not connection.user.has_permission(required): raise NotAuthorizedException(f"Se requiere permiso: {required}")@get("/reports", guards=[permission_guard], opt={"permission": "view_reports"})async def get_reports() -> dict: ...
CORS (Cross-Origin Resource Sharing)
CORS permite que tu API acepte requests desde dominios diferentes. Necesario cuando el frontend y backend están en dominios separados.
Es posible usar allow_origins=["*"] para permitir peticiones desde cualquier origen. Esta configuración puede ser útil durante el desarrollo, pero nunca debe usarse en producción ya que permite que cualquier sitio web consuma tu API sin restricciones, facilitando ataques de phishing y uso no autorizado.
Configuración centralizada
Para gestionar todas las configuraciones de tu aplicación (JWT secrets, URLs de base de datos, configuraciones de CORS, etc.) en un solo lugar, usa pydantic-settings. Esta biblioteca permite cargar configuraciones desde variables de entorno o archivos .env.
uv add pydantic-settings
Definir configuraciones
# config.pyfrom pydantic_settings import BaseSettings, SettingsConfigDictclass Settings(BaseSettings): """Configuración centralizada de la aplicación.""" # Seguridad jwt_secret: str jwt_expiration_hours: int = 24 # Base de datos database_url: str # CORS cors_allowed_origins: list[str] = ["http://localhost:3000"] # Configuración del modelo model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", )# Instancia única de configuraciónsettings = Settings()