FastAPI: Seguridad

Aprende a proteger APIs con FastAPI usando autenticación OAuth2, tokens JWT, hash de contraseñas y validación robusta de datos.

Aprende FastAPI GRATIS y certifícate

Seguridad en FastAPI

La seguridad en aplicaciones web es un aspecto fundamental que no puede pasarse por alto. FastAPI proporciona herramientas integradas que simplifican la implementación de medidas de seguridad robustas, permitiendo proteger nuestras APIs de manera eficiente y siguiendo estándares de la industria.

Fundamentos de seguridad en APIs

Las APIs REST están expuestas a múltiples amenazas de seguridad. Los ataques más comunes incluyen acceso no autorizado, inyección de código malicioso y exposición de datos sensibles. FastAPI aborda estos problemas mediante un sistema de autenticación y autorización bien estructurado.

La autenticación verifica la identidad del usuario, mientras que la autorización determina qué recursos puede acceder ese usuario autenticado. FastAPI integra ambos conceptos de forma transparente, utilizando estándares como OAuth2 y JWT (JSON Web Tokens).

Tokens de acceso y JWT

Los tokens JWT son el mecanismo más utilizado para mantener sesiones seguras en APIs modernas. Un token JWT contiene información codificada sobre el usuario y tiene una fecha de expiración que limita su validez temporal.

from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext

# Configuración de seguridad
SECRET_KEY = "tu-clave-secreta-muy-segura"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

Este ejemplo muestra cómo generar un token de acceso con una duración limitada. La biblioteca jose maneja la codificación JWT, mientras que passlib se encarga del hash de contraseñas de forma segura.

Autenticación con OAuth2

FastAPI implementa OAuth2 de manera nativa, proporcionando un flujo de autenticación estándar. El esquema OAuth2 con contraseña es el más común para APIs internas.

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_token(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Token inválido"
            )
        return username
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token inválido"
        )

El OAuth2PasswordBearer extrae automáticamente el token del header Authorization. La función verify_token valida el token y extrae la información del usuario, lanzando excepciones HTTP apropiadas si el token es inválido.

Protección de endpoints

Una vez configurada la autenticación, podemos proteger endpoints específicos utilizando el sistema de dependencias de FastAPI.

from fastapi import Depends

@app.get("/usuarios/perfil")
async def obtener_perfil(usuario_actual: str = Depends(verify_token)):
    return {"usuario": usuario_actual, "mensaje": "Perfil del usuario"}

@app.get("/admin/usuarios")
async def listar_usuarios(usuario_actual: str = Depends(verify_token)):
    # Aquí podrías verificar si el usuario tiene permisos de administrador
    return {"usuarios": ["usuario1", "usuario2"]}

Los endpoints protegidos requieren un token válido para ser accedidos. FastAPI maneja automáticamente la validación y devuelve errores HTTP 401 si la autenticación falla.

Hash de contraseñas

El almacenamiento seguro de contraseñas es crucial. Nunca debemos guardar contraseñas en texto plano. FastAPI recomienda usar passlib con el algoritmo bcrypt.

def hash_password(password: str):
    return pwd_context.hash(password)

def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)

# Ejemplo de uso en registro de usuario
@app.post("/registro")
async def registrar_usuario(username: str, password: str):
    hashed_password = hash_password(password)
    # Guardar usuario con contraseña hasheada en base de datos
    return {"mensaje": "Usuario registrado correctamente"}

El hashing es un proceso unidireccional que convierte la contraseña original en una cadena irreversible. La función verify_password compara la contraseña ingresada con el hash almacenado sin necesidad de descifrar.

Middleware de seguridad

FastAPI permite implementar middleware personalizado para añadir capas adicionales de seguridad, como limitación de velocidad o validación de headers.

from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware

# Configuración CORS para controlar acceso desde navegadores
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://mi-frontend.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)

# Validación de hosts confiables
app.add_middleware(
    TrustedHostMiddleware, 
    allowed_hosts=["mi-api.com", "*.mi-api.com"]
)

El middleware CORS controla qué dominios pueden acceder a nuestra API desde navegadores web. El TrustedHostMiddleware previene ataques de Host Header Injection validando el header Host de las peticiones.

Validación de datos de entrada

La validación robusta de datos de entrada previene inyecciones y otros ataques. FastAPI utiliza Pydantic para validar automáticamente los datos recibidos.

from pydantic import BaseModel, validator
import re

class UsuarioRegistro(BaseModel):
    username: str
    email: str
    password: str
    
    @validator('username')
    def validar_username(cls, v):
        if not re.match("^[a-zA-Z0-9_]+$", v):
            raise ValueError('Username solo puede contener letras, números y guiones bajos')
        return v
    
    @validator('password')
    def validar_password(cls, v):
        if len(v) < 8:
            raise ValueError('La contraseña debe tener al menos 8 caracteres')
        return v

Los validadores personalizados de Pydantic permiten implementar reglas de negocio específicas y prevenir la entrada de datos maliciosos o mal formateados.

Empezar curso de FastAPI

Lecciones de este módulo de FastAPI

Lecciones de programación del módulo Seguridad del curso de FastAPI.