Cómo proteger una API Flask con JWT

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Proteção de API Flask usando autenticação JWT em Python

Una API Flask necesita verificar quién realiza cada solicitud antes de permitir acceso a perfiles, pedidos, archivos o paneles privados. JSON Web Token, conocido como JWT, es un formato compacto para transportar afirmaciones firmadas entre un cliente y un servidor. Utilizado correctamente, permite crear autenticación sin guardar una sesión tradicional en memoria. Sin embargo, JWT no cifra automáticamente la información y no sustituye controles como HTTPS, contraseñas seguras, expiración y revocación.

Para seguir esta guía conviene conocer el funcionamiento básico de Flask, cómo proteger secretos con variables de entorno, cómo realizar el hash de contraseñas con bcrypt y cómo manejar excepciones en Python.

Qué contiene un JWT

Un token tiene tres partes separadas por puntos: encabezado, carga útil y firma. El encabezado indica el algoritmo. La carga útil incluye afirmaciones como el identificador del usuario, la fecha de expiración y el tipo de token. La firma permite detectar modificaciones. El contenido puede decodificarse fácilmente, por lo que nunca debes guardar contraseñas, claves privadas, números de tarjeta u otros secretos dentro del token.

El estándar está definido en el RFC 7519 de la IETF. La firma demuestra integridad y autenticidad cuando la clave permanece protegida; no convierte la carga útil en información confidencial.

Instalar las dependencias

python -m venv .venv
.venv\Scripts\activate
python -m pip install Flask Flask-JWT-Extended bcrypt

En macOS o Linux activa el entorno con source .venv/bin/activate. Guarda las versiones en un archivo de requisitos y revisa periódicamente actualizaciones de seguridad.

Configurar la aplicación

import os
from datetime import timedelta
from flask import Flask
from flask_jwt_extended import JWTManager

app = Flask(__name__)

clave = os.getenv("JWT_SECRET_KEY")
if not clave:
    raise RuntimeError("Falta la variable JWT_SECRET_KEY")

app.config["JWT_SECRET_KEY"] = clave
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=15)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=7)

jwt = JWTManager(app)

Genera una clave larga y aleatoria. No uses valores como secret ni publiques la clave en Git. En producción, almacénala en el gestor de secretos de la plataforma.

Modelo de usuario simplificado

En una aplicación real los usuarios estarán en una base de datos. Para entender el flujo utilizaremos un diccionario:

USUARIOS = {}

La contraseña se guardará como hash, nunca como texto plano.

Registrar un usuario

import bcrypt
from flask import request, jsonify

@app.post("/registro")
def registro():
    datos = request.get_json(silent=True) or {}
    email = str(datos.get("email", "")).strip().lower()
    password = str(datos.get("password", ""))

    if not email or len(password) < 12:
        return jsonify(error="Datos inválidos"), 400

    if email in USUARIOS:
        return jsonify(error="El usuario ya existe"), 409

    hash_password = bcrypt.hashpw(
        password.encode("utf-8"),
        bcrypt.gensalt()
    )

    USUARIOS[email] = {"password": hash_password, "activo": True}
    return jsonify(mensaje="Usuario creado"), 201

La validación debe ser más completa en producción. También aplica límites de frecuencia para evitar registros automatizados y no reveles detalles que faciliten enumerar cuentas.

Crear el endpoint de inicio de sesión

from flask_jwt_extended import create_access_token, create_refresh_token

@app.post("/login")
def login():
    datos = request.get_json(silent=True) or {}
    email = str(datos.get("email", "")).strip().lower()
    password = str(datos.get("password", ""))

    usuario = USUARIOS.get(email)
    valido = usuario and bcrypt.checkpw(
        password.encode("utf-8"),
        usuario["password"]
    )

    if not valido or not usuario.get("activo"):
        return jsonify(error="Credenciales inválidas"), 401

    access = create_access_token(
        identity=email,
        additional_claims={"rol": "usuario"}
    )
    refresh = create_refresh_token(identity=email)

    return jsonify(access_token=access, refresh_token=refresh)

El mensaje de error es deliberadamente genérico. No debe indicar si el correo existe o si únicamente falló la contraseña.

Proteger rutas

from flask_jwt_extended import jwt_required, get_jwt_identity, get_jwt

@app.get("/perfil")
@jwt_required()
def perfil():
    email = get_jwt_identity()
    claims = get_jwt()
    return jsonify(email=email, rol=claims.get("rol"))

El cliente debe enviar el token:

Authorization: Bearer TOKEN_DE_ACCESO

La biblioteca valida firma, expiración y formato antes de ejecutar la función. La documentación oficial de Flask-JWT-Extended cubre cookies, encabezados, revocación, tokens frescos y configuración avanzada.

Renovar el token de acceso

@app.post("/refresh")
@jwt_required(refresh=True)
def refresh():
    identidad = get_jwt_identity()
    nuevo = create_access_token(identity=identidad)
    return jsonify(access_token=nuevo)

El token de acceso debe durar poco tiempo. El token de refresco puede durar más, pero necesita mayor protección. Si se utiliza en una aplicación web, una cookie HttpOnly, Secure y con una política SameSite adecuada puede reducir la exposición a JavaScript. Cuando autenticas mediante cookies, también debes protegerte contra CSRF.

Revocar tokens

JWT suele describirse como “sin estado”, pero cerrar sesión de forma inmediata o bloquear un token robado requiere guardar identificadores revocados. Cada token incluye un jti único.

from flask_jwt_extended import get_jwt

TOKENS_REVOCADOS = set()

@jwt.token_in_blocklist_loader
def token_revocado(jwt_header, jwt_payload):
    return jwt_payload["jti"] in TOKENS_REVOCADOS

@app.delete("/logout")
@jwt_required()
def logout():
    TOKENS_REVOCADOS.add(get_jwt()["jti"])
    return jsonify(mensaje="Sesión cerrada")

Un conjunto en memoria se pierde cuando el servidor reinicia y no se comparte entre procesos. En producción utiliza Redis o una tabla de base de datos con expiración.

Comprobar roles y permisos

from functools import wraps


def requiere_rol(rol):
    def decorador(funcion):
        @wraps(funcion)
        @jwt_required()
        def envoltura(*args, **kwargs):
            claims = get_jwt()
            if claims.get("rol") != rol:
                return jsonify(error="Acceso denegado"), 403
            return funcion(*args, **kwargs)
        return envoltura
    return decorador

@app.get("/admin")
@requiere_rol("admin")
def admin():
    return jsonify(mensaje="Panel administrativo")

No confíes únicamente en el rol almacenado durante semanas en un token. Si los permisos pueden cambiar, utiliza tokens cortos o consulta el estado actual del usuario para operaciones sensibles.

Manejadores de errores

@jwt.expired_token_loader
def token_expirado(header, payload):
    return jsonify(error="El token expiró"), 401

@jwt.invalid_token_loader
def token_invalido(motivo):
    return jsonify(error="Token inválido"), 401

@jwt.unauthorized_loader
def falta_token(motivo):
    return jsonify(error="Falta autenticación"), 401

Registra detalles técnicos en el servidor, pero evita devolver información interna al cliente.

HTTPS es obligatorio

Sin HTTPS, un atacante en la red puede capturar el token y utilizarlo hasta que expire. Configura TLS en el proxy inverso o la plataforma de despliegue. Nunca envíes JWT en parámetros de URL, porque pueden aparecer en historial, registros y encabezados de referencia.

Algoritmos y claves

Para una sola API, un algoritmo simétrico con una clave fuerte puede ser suficiente. Cuando varios servicios verifican tokens sin necesitar capacidad para firmarlos, considera algoritmos asimétricos: el emisor conserva la clave privada y distribuye la pública. Define explícitamente los algoritmos aceptados y no confíes en valores enviados por el cliente.

Pruebas básicas

def test_perfil_requiere_token(cliente):
    respuesta = cliente.get("/perfil")
    assert respuesta.status_code == 401

Prueba credenciales incorrectas, expiración, revocación, roles, tokens modificados, usuarios desactivados y solicitudes sin encabezado. Automatizar estas pruebas reduce regresiones de seguridad.

Errores frecuentes

Los fallos más peligrosos son usar una clave débil, guardar datos sensibles en la carga útil, crear tokens sin expiración, omitir HTTPS, guardar contraseñas sin hash, no limitar intentos de login y tratar un token de refresco como si fuera un token normal. También es incorrecto pensar que cerrar la aplicación cliente invalida automáticamente un JWT.

Conclusión

JWT puede ser una solución eficaz para proteger APIs Flask cuando se combina con contraseñas hasheadas, claves seguras, expiraciones cortas, tokens de refresco protegidos, revocación y autorización por permisos. La biblioteca Flask-JWT-Extended reduce el código necesario, pero las decisiones de seguridad siguen siendo responsabilidad de la aplicación. Diseña el flujo completo, prueba los casos adversos y utiliza HTTPS en todos los entornos públicos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Enums en Python: evita valores mágicos y errores

    Aprende enums en Python con Enum, IntEnum, StrEnum, auto, unique, Flag, validación, JSON, bases de datos y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Medição de tempo de execução de código Python com timeit
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    timeit en Python: mide el rendimiento correctamente

    Aprende timeit en Python para medir funciones, usar repeat y Timer, comparar implementaciones, reducir ruido y evitar benchmarks engañosos.

    Ler mais

    Tempo de leitura: 6 minutos
    11/07/2026
    Uso de dataclasses para simplificar classes em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Dataclasses en Python: clases de datos más limpias

    Aprende dataclasses en Python: campos, valores predeterminados, field, frozen, order, __post_init__, herencia, asdict y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Criação de hashes seguros para senhas usando Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Hash de contraseñas con bcrypt en Python

    Aprende bcrypt en Python para almacenar contraseñas: salt, coste, hash, verificación, rehash, límites, errores y recomendaciones de seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Problemas de travamento com threading em scripts Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Threading en Python: evita que tus scripts se bloqueen

    Aprende threading en Python: hilos, tareas de entrada y salida, join, locks, colas, ThreadPoolExecutor, errores y límites del GIL.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Dicas para otimizar scripts Python lentos
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Por qué tu script Python es lento y cómo mejorarlo

    Descubre por qué un script Python es lento y cómo mejorarlo con cProfile, timeit, estructuras correctas, generadores y mejores algoritmos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026