Saltar al contenido

Lección 10 de 25 · Lectura y práctica

Quiz: Seguridad y Autenticación en FastAPI

Tipo de lección: QuizConcepto claveLa seguridad en APIs modernas se basa en tres pilares fundamentales: autenticación (verificar quién eres), autorización (qué puedes hacer) y protección de datos (cómo se maneja la información.

Del concepto a la práctica

Taller de esta lección

Usa una matriz de decisión para comprobar seguridad: identidad válida, permiso suficiente, entrada válida y recurso existente. Cada dimensión debe tener un resultado observable. La matriz evita confundir autenticación con autorización y permite revisar que un error temprano no filtre datos.

Ejemplo resuelto

Para editar el artículo 31: token ausente produce 401; token válido sin permiso produce 403; permiso válido con cuerpo incorrecto produce 422; todo correcto produce 200.

Pruébalo

  1. Escribe las cuatro filas de la matriz, cambiando una sola condición cada vez; el token ausente se prueba con cuerpo válido y el cuerpo incorrecto con token y permiso válidos.

  2. Asigna un usuario y artículo concretos.

  3. Ejecuta una petición por fila.

  4. Comprueba cuerpo, código y ausencia de escritura en fallos.

Comprueba tu resultado

La secuencia esperada es 401, 403, 422 y 200, con el artículo cambiado solo en la última fila.

Error frecuente: responder 200 con un mensaje de error. Corrígelo usando códigos HTTP coherentes y una prueba que falle si el código es incorrecto.

Capas de acceso, actualización, copias y respuesta a incidentes alrededor de un sistema.
CursaloMapa de práctica
La seguridad necesita varias capas

El control de acceso, las actualizaciones y las copias de seguridad cubren riesgos distintos. Una alerta necesita un procedimiento de respuesta para contener y recuperar el servicio. Varias capas reducen la exposición, pero ninguna garantiza seguridad absoluta.

Ver la infografía en tamaño completo ↗

Tipo de lección: Quiz

Concepto clave

La seguridad en APIs modernas se basa en tres pilares fundamentales: autenticación (verificar quién eres), autorización (qué puedes hacer) y protección de datos (cómo se maneja la información sensible). En FastAPI, esto se implementa mediante middlewares, dependencias y estándares como OAuth2 con JWT.

Imagina una empresa con acceso controlado: primero muestras tu credencial (autenticación), luego el sistema verifica a qué áreas tienes permiso (autorización), y finalmente asegura que no puedas ver información confidencial de otros departamentos (protección de datos). FastAPI proporciona herramientas nativas para construir este flujo de manera eficiente y escalable.

Cómo funciona en la práctica

El proceso típico en FastAPI sigue estos pasos:

  1. El cliente envía credenciales (usuario/contraseña) a un endpoint de login
  2. El servidor valida las credenciales contra una base de datos
  3. Si son válidas, genera un token JWT firmado
  4. El cliente incluye este token en el header Authorization de solicitudes posteriores
  5. FastAPI valida automáticamente el token usando dependencias
  6. Se verifica si el usuario tiene permisos para la acción solicitada

Este flujo permite stateless authentication, ideal para arquitecturas microservicios.

Código en acción

Implementación básica de autenticación JWT:

from datetime import datetime, timedelta
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

# Configuración
SECRET_KEY = "tu_clave_secreta_super_segura"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

app = FastAPI()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: Optional[str] = None

# Función para verificar contraseña
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

# Función para crear token
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# Dependencia para obtener usuario actual
async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    return token_data

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    # Aquí iría la validación real contra base de datos
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me")
async def read_users_me(current_user: TokenData = Depends(get_current_user)):
    return {"username": current_user.username}

Implementación mejorada con roles y permisos:

from enum import Enum
from fastapi import Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

class Role(str, Enum):
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"

security = HTTPBearer()

def has_permission(required_role: Role):
    async def permission_dependency(
        credentials: HTTPAuthorizationCredentials = Security(security)
    ):
        token = credentials.credentials
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            user_role = payload.get("role")
            
            # Jerarquía de permisos
            role_hierarchy = {Role.GUEST: 1, Role.USER: 2, Role.ADMIN: 3}
            
            if role_hierarchy.get(Role(user_role), 0) < role_hierarchy[required_role]:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Insufficient permissions"
                )
            return payload
        except JWTError:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid token"
            )
    return permission_dependency

@app.get("/admin/dashboard")
async def admin_dashboard(
    user_data = Depends(has_permission(Role.ADMIN))
):
    return {"message": "Welcome to admin dashboard", "user": user_data}

Errores comunes

  • No validar expiración de tokens: Los tokens JWT deben tener expiración configurada y validarse en cada request. Sin esto, un token robado es válido indefinidamente.
  • Almacenar secretos en código: Claves secretas como SECRET_KEY deben estar en variables de entorno, nunca hardcodeadas en el código fuente.
  • Falta de rate limiting: Endpoints de login deben tener límites de intentos para prevenir ataques de fuerza bruta.
  • No usar HTTPS en producción: Sin HTTPS, tokens viajan en texto plano, exponiéndolos a interceptación.
  • Permisos demasiado amplios: Asignar permisos de administrador por defecto o no implementar principio de menor privilegio.

Checklist de dominio

  1. Implementé autenticación JWT con expiración configurable
  2. Protegí endpoints con dependencias que validan tokens automáticamente
  3. Implementé sistema de roles y permisos con jerarquía
  4. Configuré variables de entorno para claves secretas
  5. Agregué rate limiting a endpoints críticos
  6. Validé que todos los tokens incluyan claim "exp"
  7. Documenté los flujos de autenticación en OpenAPI/Swagger

Implementa un sistema de autenticación con refresh tokens

En este ejercicio, mejorarás el sistema de autenticación básico para incluir refresh tokens, una práctica esencial en APIs profesionales.

  1. Crea un nuevo endpoint /refresh que acepte un refresh token válido y devuelva un nuevo access token
  2. Modifica el endpoint /token para que devuelva tanto access_token como refresh_token
  3. Implementa lógica para invalidar refresh tokens cuando sean comprometidos
  4. Asegúrate que los refresh tokens tengan mayor tiempo de expiración que los access tokens (ej: 7 días vs 30 minutos)
  5. Crea un endpoint /logout que permita invalidar ambos tokens
  6. Agrega validación para prevenir reuso de refresh tokens

Entrega: Código completo con los 3 endpoints funcionando y pruebas que demuestren el flujo completo.

Pistas
  • Considera almacenar refresh tokens en una base de datos para poder invalidarlos
  • Usa diferentes claves secretas para access y refresh tokens
  • Implementa blacklist para tokens invalidados

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con Dominio Profesional de FastAPI: Construcción de APIs Escalables y Seguras

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.