Saltar al contenido

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

Práctica: Construir un Sistema de Login con Roles y Protección

Concepto claveEn el desarrollo de APIs profesionales, la autenticación y autorización son dos pilares fundamentales que trabajan en conjunto para proteger los recursos. La autenticación responde a la pregunta "¿Quién eres?".

Del concepto a la práctica

Taller de esta lección

Un flujo completo de login debe conectar identidad, rol y recurso protegido sin mezclar responsabilidades. Practica con dos usuarios locales: marta como editora y leo como lector. El login entrega identidad; la dependencia la recupera; la autorización decide qué operación puede realizar.

Ejemplo resuelto

Marta obtiene un token y crea un borrador. Leo obtiene otro token, puede leerlo, pero recibe 403 al publicarlo. La base de datos conserva un solo borrador porque la operación denegada no llegó al servicio de escritura.

Pruébalo

  1. Crea ambos usuarios con hashes válidos y permisos locales; mantén el borrador como lectura permitida para Leo únicamente en esta política de laboratorio.

  2. Implementa el endpoint de token.

  3. Protege lectura y escritura con dependencias separadas.

  4. Ejecuta login, lectura y publicación con ambos tokens.

Comprueba tu resultado

Marta recibe 200 al publicar; Leo recibe 403; una contraseña incorrecta recibe 401.

Error frecuente: confiar en un role enviado por el cliente. Corrígelo obteniendo el rol desde la identidad validada y registra el usuario que efectuó cada operación.

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 ↗

Concepto clave

En el desarrollo de APIs profesionales, la autenticación y autorización son dos pilares fundamentales que trabajan en conjunto para proteger los recursos. La autenticación responde a la pregunta "¿Quién eres?" mediante la verificación de credenciales, como un usuario y contraseña. Una vez autenticado, la autorización determina "¿Qué puedes hacer?" basándose en roles o permisos asignados.

Imagina un edificio de oficinas: la autenticación es como mostrar tu identificación en recepción para entrar, mientras que la autorización son las llaves específicas que te permiten acceder solo a ciertas salas según tu puesto. En FastAPI, implementamos esto combinando OAuth2 con contraseña para autenticación y dependencias personalizadas para autorización, creando un sistema robusto y escalable.

Cómo funciona en la práctica

El flujo comienza cuando un usuario envía sus credenciales a un endpoint de login. FastAPI valida estas credenciales contra una base de datos y, si son correctas, genera un token JWT (JSON Web Token) que contiene información del usuario y sus roles. Este token se envía al cliente y debe incluirse en las cabeceras de las solicitudes posteriores.

Para proteger endpoints específicos, creamos dependencias que: 1) Verifican la validez del token, 2) Extraen la información del usuario, y 3) Comprueban si tiene los roles necesarios. Por ejemplo, un endpoint para eliminar usuarios podría requerir el rol "admin", mientras que uno para ver perfiles solo requeriría estar autenticado. Este enfoque permite un control granular sobre quién puede acceder a cada recurso.

Código en acción

Primero, configuramos la autenticación con OAuth2 y JWT:

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

app = FastAPI()

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

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

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

class User(BaseModel):
    username: str
    email: str
    full_name: str
    disabled: bool
    roles: list

# 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 JWT
def create_access_token(data: dict, expires_delta: 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

Luego, implementamos la autorización con roles:

from fastapi import Depends
from typing import List

# Dependencia para obtener usuario actual
def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Credenciales inválidas",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        roles: list = payload.get("roles", [])
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    return {"username": username, "roles": roles}

# Dependencia para verificar roles
def require_roles(required_roles: List[str]):
    def role_checker(current_user: dict = Depends(get_current_user)):
        user_roles = current_user.get("roles", [])
        if not any(role in user_roles for role in required_roles):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Permisos insuficientes"
            )
        return current_user
    return role_checker

# Endpoint protegido con roles
@app.get("/admin/dashboard")
async def admin_dashboard(current_user: dict = Depends(require_roles(["admin"]))):
    return {"message": "Bienvenido al panel de administración", "user": current_user}

@app.get("/user/profile")
async def user_profile(current_user: dict = Depends(get_current_user)):
    return {"message": "Perfil de usuario", "user": current_user}

Errores comunes

  • Almacenar contraseñas en texto plano: Nunca guardes contraseñas sin encriptar. Usa bibliotecas como passlib con bcrypt para hashearlas de forma segura antes de almacenarlas en la base de datos.
  • Exponer información sensible en tokens JWT: Los tokens pueden ser decodificados (aunque no modificados si están firmados). No incluyas datos sensibles como contraseñas o información personal crítica en el payload del token.
  • No validar roles en el backend: Aunque el frontend pueda ocultar elementos basados en roles, siempre valida los permisos en el backend. Un usuario malintencionado podría enviar solicitudes directamente a la API.
  • Usar tiempos de expiración muy largos: Los tokens JWT deben tener una vida útil corta (minutos u horas) para minimizar el riesgo si son comprometidos. Implementa un sistema de refresh tokens para sesiones prolongadas.
  • Falta de rate limiting en endpoints de login: Los endpoints de autenticación son objetivos comunes para ataques de fuerza bruta. Implementa límites de intentos para prevenir estos ataques.

Checklist de dominio

  1. Puedo implementar autenticación OAuth2 con contraseña y tokens JWT en FastAPI
  2. Sé crear y verificar hashes de contraseñas usando bcrypt
  3. Puedo proteger endpoints con dependencias que verifican tokens y roles
  4. Entiendo la diferencia entre autenticación (verificar identidad) y autorización (verificar permisos)
  5. Sé implementar un sistema de roles jerárquicos (ej: admin > editor > user)
  6. Puedo manejar correctamente errores de autenticación (401) y autorización (403)
  7. Sé configurar tiempos de expiración adecuados para tokens de acceso y refresh

Implementa un sistema de gestión de usuarios con roles múltiples

En este ejercicio práctico, construirás un sistema completo de gestión de usuarios con autenticación y autorización basada en roles. Sigue estos pasos:

  1. Crea un nuevo proyecto FastAPI con las siguientes dependencias: fastapi, uvicorn, python-jose[cryptography], passlib[bcrypt], python-multipart
  2. Implementa un modelo de usuario en una base de datos SQL (puedes usar SQLAlchemy o databases) con los campos: id, username, email, hashed_password, is_active, roles (lista de strings)
  3. Crea los siguientes endpoints:
    • POST /register - Permite registrar nuevos usuarios (solo rol "user" por defecto)
    • POST /login - Devuelve un token JWT con los roles del usuario
    • GET /users/me - Devuelve el perfil del usuario autenticado
    • PUT /users/{user_id} - Permite a usuarios con rol "admin" actualizar cualquier usuario, o a usuarios normales actualizar solo su propio perfil
    • DELETE /users/{user_id} - Solo accesible por usuarios con rol "admin"
  4. Implementa un sistema de roles que incluya: "admin" (acceso completo), "editor" (puede crear/editar contenido), "user" (acceso básico)
  5. Añade rate limiting al endpoint /login (máximo 5 intentos por minuto por IP)
  6. Escribe tests para verificar:
    • Un usuario normal no puede acceder a endpoints de admin
    • Un usuario puede actualizar solo su propio perfil
    • El token JWT expira correctamente
Pistas
  • Usa el decorador @app.middleware("http") para implementar el rate limiting basado en IP
  • Considera usar una tabla separada para la relación muchos-a-muchos entre usuarios y roles si necesitas escalabilidad
  • Para el endpoint PUT /users/{user_id}, crea una dependencia que verifique si el usuario es admin O si está actualizando su propio perfil

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.