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

Implementar Autenticación JWT con OAuth2 y Contraseñas Hasheadas

Concepto claveLa autenticación JWT con OAuth2 y contraseñas hasheadas es el estándar industrial para proteger APIs modernas. Imagina que tu API es un edificio de oficinas: OAuth2 es el sistema de seguridad que verifica credenciales en la entrada, JWT es la credencial temporal que se entrega tras la verificación, y el hashing de contraseñas es la caja fuerte donde se guardan las llaves maestras de forma irreversible.En términos técnicos, OAuth2 define los flujos de autorización, JWT proporciona t
Tiempo de estudio
20 Min

Concepto clave

La autenticación JWT con OAuth2 y contraseñas hasheadas es el estándar industrial para proteger APIs modernas. Imagina que tu API es un edificio de oficinas: OAuth2 es el sistema de seguridad que verifica credenciales en la entrada, JWT es la credencial temporal que se entrega tras la verificación, y el hashing de contraseñas es la caja fuerte donde se guardan las llaves maestras de forma irreversible.

En términos técnicos, OAuth2 define los flujos de autorización, JWT proporciona tokens portátiles y auto-contenidos, y el hashing (con algoritmos como bcrypt) transforma contraseñas en valores irreversibles. Esta combinación resuelve tres problemas críticos: autenticación segura, autorización granular y protección de datos sensibles. En producción, esto se traduce en APIs que resisten ataques comunes como fuerza bruta, inyección de tokens y exposición de credenciales.

Cómo funciona en la práctica

El flujo completo sigue estos pasos:

  1. El cliente envía credenciales (usuario/contraseña) al endpoint de login
  2. El servidor verifica las credenciales contra la base de datos (contraseña hasheada)
  3. Si son válidas, genera un JWT con claims específicos
  4. El token se devuelve al cliente en la respuesta
  5. El cliente incluye el token en el header Authorization de peticiones subsiguientes
  6. El servidor valida el token en cada request protegido
  7. El token expira según configuración, forzando reautenticación

Este patrón permite escalabilidad horizontal, ya que el token contiene toda la información necesaria sin requerir sesiones en el servidor.

Código en acción

Implementación completa con FastAPI, OAuth2PasswordBearer y bcrypt:

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_cambia_en_produccion"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

# Modelos
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None

class UserInDB(User):
hashed_password: str

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

# Base de datos simulada
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "[email protected]",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # "secret"
"disabled": False,
}
}

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

def get_password_hash(password):
return pwd_context.hash(password)

def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)

def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user

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

@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Usuario o contraseña incorrectos",
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"}

async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="No se pudieron validar las credenciales",
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
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user

@app.get("/users/me/")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user

Refactorización para producción: antes usábamos una clave simple, después implementamos variables de entorno y rotación de claves:

# ANTES: Clave hardcodeada
SECRET_KEY = "clave_facil_de_adivinar"

# DESPUES: Configuración segura
import os
from dotenv import load_dotenv

load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY")
if not SECRET_KEY:
raise ValueError("SECRET_KEY no configurada en variables de entorno")

Errores comunes

  • No usar salt en el hashing: bcrypt lo incluye automáticamente, pero otros algoritmos no. Siempre verifica que tu contexto CryptContext use esquemas modernos.
  • Exponer información sensible en el JWT: Los tokens viajan en headers, pero pueden ser decodificados (aunque no modificados). Nunca incluyas contraseñas o datos críticos.
  • Tokens con expiración muy larga: En producción, 30 minutos es razonable para access tokens. Implementa refresh tokens para sesiones prolongadas.
  • Validación incompleta del token: Siempre verifica expiración, issuer (iss) y audience (aud) cuando corresponda.
  • Almacenar contraseñas en texto plano temporalmente: Hash inmediatamente al recibirlas, incluso en memoria.

Checklist de dominio

  1. Configuré OAuth2PasswordBearer con endpoint personalizado
  2. Implementé bcrypt para hashing y verificación de contraseñas
  3. Generé JWTs con expiración configurable y claims relevantes
  4. Validé tokens en cada request protegido, manejando excepciones JWT
  5. Protegí rutas con dependencias de autenticación
  6. Use variables de entorno para claves secretas
  7. Implementé manejo seguro de errores (HTTP 401, 403 apropiados)

Implementa un sistema de registro y login con JWT, OAuth2 y hashing seguro


Extiende el ejemplo proporcionado para crear un sistema completo de autenticación:

  1. Crea un endpoint POST /register que acepte username, email y password. Hashea la contraseña con bcrypt y almacena el usuario en una base de datos simulada.
  2. Modifica el endpoint /token para que verifique contra la nueva base de datos.
  3. Añade un endpoint GET /users/{username} protegido que solo pueda acceder el usuario dueño de ese perfil o un admin.
  4. Implementa logout invalidando tokens (simula con una blacklist en memoria).
  5. Añade validación: password mínimo 8 caracteres, con letras y números.

Entrega el código completo en un archivo main.py funcional.


Pistas
  • Usa Pydantic para validar los datos de registro
  • Considera agregar un campo 'role' a los usuarios para implementar autorización
  • Para la blacklist, puedes usar un set en memoria pero en producción usa Redis

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Texto Lessons

#1

Diseñar la Arquitectura y Especificaciones del Proyecto

#2

Configurar un Proyecto FastAPI con Estructura Modular

#3

Implementar Autenticación JWT con OAuth2 y Contraseñas Hasheadas

#4

Conectar FastAPI a Bases de Datos SQL con SQLAlchemy y Async

#5

Escribir Tests Unitarios e Integración con Pytest y FastAPI TestClient

#6

Implementar Endpoints con Autenticación y Autorización

#7

Implementar Inyección de Dependencias para Servicios Reutilizables

#8

Configurar Autorización Basada en Roles y Permisos

#9

Realizar Operaciones CRUD Avanzadas con Relaciones y Transacciones

#10

Generar Documentación Automática con OpenAPI y Personalización

#11

Integrar Bases de Datos y Manejar Operaciones Complejas

#12

Crear Eventos de Inicio y Cierre para Gestión de Recursos

#13

Aplicar Medidas de Seguridad: CORS, Rate Limiting y Validación

#14

Integrar MongoDB para Escenarios NoSQL con Motor Asíncrono

#15

Desplegar FastAPI en Producción con Docker y Servicios Cloud

#16

Práctica: Configurar CI/CD y Desplegar una API en un Entorno Cloud

#17

Escribir Tests y Documentar la API para Producción

#18

Práctica: Diseñar una API Modular con Dependencias Personalizadas

#19

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

#20

Práctica: Desarrollar un API con Multiples Bases de Datos y Transacciones

#21

Quiz: Testing, Documentación y Despliegue

#22

Desplegar y Validar el Proyecto en un Entorno Simulado

#23

Quiz: Evaluación de Arquitectura y Configuración

#24

Quiz: Seguridad y Autenticación en FastAPI

#25

Quiz: Bases de Datos y Operaciones en FastAPI

Ver full lessons Revisar curso learning pagina
Texto Leccion 1/25
Estas viendo
Implementar Autenticación JWT con OAuth2 y Contraseñas Hasheadas
Hablar por WhatsAppContactar por WhatsApp