Flask
Tutorial Flask: Flask-JWT-Extended
Aprende a configurar Flask-JWT-Extended para implementar autenticación JWT segura en APIs REST con tokens de acceso y refresh.
Aprende Flask y certifícateConfiguración JWT
La autenticación basada en tokens JWT representa un cambio fundamental respecto a los sistemas tradicionales de sesiones web. Mientras que las aplicaciones web convencionales mantienen el estado del usuario mediante sesiones almacenadas en el servidor, las APIs REST utilizan tokens JWT que contienen toda la información necesaria de forma autocontenida.
Esta diferencia es crucial: en una aplicación web tradicional con Flask-Login, el servidor mantiene una sesión activa y utiliza cookies para identificar al usuario. En cambio, con JWT para APIs, cada petición incluye un token que el servidor puede verificar de forma independiente, sin necesidad de mantener estado entre peticiones.
Instalación y configuración inicial
Flask-JWT-Extended es la extensión estándar para implementar autenticación JWT en Flask. Su instalación se realiza mediante pip:
pip install Flask-JWT-Extended
La configuración básica requiere establecer una clave secreta que se utilizará para firmar y verificar los tokens. Esta clave debe ser única, segura y mantenerse en secreto:
from flask import Flask
from flask_jwt_extended import JWTManager
app = Flask(__name__)
# Configuración JWT obligatoria
app.config['JWT_SECRET_KEY'] = 'tu-clave-secreta-super-segura'
# Inicializar JWT Manager
jwt = JWTManager(app)
Configuraciones avanzadas de seguridad
Para entornos de producción, es fundamental configurar parámetros adicionales que mejoren la seguridad del sistema. La duración de los tokens es uno de los aspectos más importantes:
from datetime import timedelta
# Configuraciones de tiempo de vida
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=30)
# Configuración del algoritmo de firma
app.config['JWT_ALGORITHM'] = 'HS256'
# Ubicación del token en las peticiones
app.config['JWT_TOKEN_LOCATION'] = ['headers']
app.config['JWT_HEADER_NAME'] = 'Authorization'
app.config['JWT_HEADER_TYPE'] = 'Bearer'
La configuración de ubicación del token determina dónde debe enviarse el JWT en las peticiones HTTP. El método más común y seguro es incluirlo en el header Authorization
con el prefijo Bearer
.
Gestión de tokens inválidos y expirados
Flask-JWT-Extended proporciona manejadores de errores personalizables para diferentes situaciones relacionadas con tokens. Estos manejadores permiten devolver respuestas JSON apropiadas cuando ocurren errores de autenticación:
from flask_jwt_extended import jwt_required, get_jwt_identity
from flask import jsonify
# Manejador para tokens expirados
@jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return jsonify({
'error': 'Token expirado',
'message': 'El token de acceso ha caducado'
}), 401
# Manejador para tokens inválidos
@jwt.invalid_token_loader
def invalid_token_callback(error):
return jsonify({
'error': 'Token inválido',
'message': 'La firma del token no es válida'
}), 401
# Manejador para peticiones sin token
@jwt.unauthorized_loader
def missing_token_callback(error):
return jsonify({
'error': 'Token requerido',
'message': 'Se requiere un token de acceso válido'
}), 401
Configuración de claims personalizados
Los claims adicionales permiten incluir información extra en el token JWT. Esto es útil para almacenar datos como roles de usuario, permisos o metadatos que se necesiten en múltiples endpoints:
# Agregar claims personalizados al token
@jwt.additional_claims_loader
def add_claims_to_access_token(identity):
# Aquí podrías consultar la base de datos para obtener roles
# Por simplicidad, usamos un ejemplo estático
if identity == 'admin@ejemplo.com':
return {
'role': 'admin',
'permissions': ['read', 'write', 'delete']
}
return {
'role': 'user',
'permissions': ['read']
}
# Función para verificar si el usuario es administrador
def is_admin():
claims = get_jwt()
return claims.get('role') == 'admin'
Variables de entorno para producción
En aplicaciones reales, las configuraciones sensibles como la clave secreta deben gestionarse mediante variables de entorno. Esto evita exponer información crítica en el código fuente:
import os
from datetime import timedelta
class Config:
# Clave secreta desde variable de entorno
JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY') or 'clave-desarrollo-no-usar-en-produccion'
# Configuraciones de tiempo
JWT_ACCESS_TOKEN_EXPIRES = timedelta(
minutes=int(os.environ.get('JWT_ACCESS_EXPIRES_MINUTES', 60))
)
# Configuración de base de datos para blacklist
JWT_BLACKLIST_ENABLED = True
JWT_BLACKLIST_TOKEN_CHECKS = ['access', 'refresh']
# Aplicar configuración
app.config.from_object(Config)
Esta estructura de configuración permite adaptar el comportamiento de JWT según el entorno de ejecución, manteniendo la flexibilidad necesaria para desarrollo, testing y producción sin comprometer la seguridad.
Tokens de acceso y refresh
Los tokens de acceso y refresh constituyen un sistema de doble capa que mejora significativamente la seguridad de las APIs REST. Este mecanismo permite mantener sesiones seguras sin comprometer la experiencia del usuario, implementando un equilibrio entre seguridad y usabilidad.
El token de acceso es de corta duración y se utiliza para autenticar cada petición a la API. Por su parte, el token de refresh tiene una vida útil más larga y su único propósito es generar nuevos tokens de acceso cuando estos expiran.
Creación de endpoints de autenticación
La implementación práctica comienza con la creación de endpoints que generen ambos tipos de tokens. El endpoint de login debe devolver tanto el token de acceso como el de refresh:
from flask import Flask, request, jsonify
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
jwt_required,
get_jwt_identity
)
from werkzeug.security import check_password_hash
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
email = data.get('email')
password = data.get('password')
# Verificar credenciales (ejemplo simplificado)
user = User.query.filter_by(email=email).first()
if user and check_password_hash(user.password_hash, password):
# Crear ambos tokens
access_token = create_access_token(identity=email)
refresh_token = create_refresh_token(identity=email)
return jsonify({
'access_token': access_token,
'refresh_token': refresh_token,
'user': {
'id': user.id,
'email': user.email
}
}), 200
return jsonify({'error': 'Credenciales inválidas'}), 401
Renovación automática de tokens
El endpoint de refresh permite obtener un nuevo token de acceso utilizando el token de refresh. Este proceso es fundamental para mantener la sesión activa sin requerir que el usuario vuelva a autenticarse:
@app.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)
def refresh():
# Obtener la identidad del token de refresh
current_user = get_jwt_identity()
# Crear nuevo token de acceso
new_access_token = create_access_token(identity=current_user)
return jsonify({
'access_token': new_access_token
}), 200
El decorador @jwt_required(refresh=True)
especifica que este endpoint requiere un token de refresh válido en lugar de un token de acceso. Esto garantiza que solo los tokens de refresh puedan utilizarse para generar nuevos tokens de acceso.
Protección de endpoints con tokens de acceso
Los endpoints protegidos utilizan el decorador @jwt_required()
sin parámetros adicionales, lo que indica que requieren un token de acceso válido:
@app.route('/profile', methods=['GET'])
@jwt_required()
def get_profile():
current_user_email = get_jwt_identity()
# Buscar información del usuario
user = User.query.filter_by(email=current_user_email).first()
if not user:
return jsonify({'error': 'Usuario no encontrado'}), 404
return jsonify({
'id': user.id,
'email': user.email,
'name': user.name,
'created_at': user.created_at.isoformat()
}), 200
@app.route('/protected-data', methods=['GET'])
@jwt_required()
def get_protected_data():
current_user = get_jwt_identity()
return jsonify({
'message': f'Datos protegidos para {current_user}',
'timestamp': datetime.utcnow().isoformat()
}), 200
Implementación de blacklist para tokens
La revocación de tokens es esencial para casos como logout o compromiso de seguridad. Flask-JWT-Extended permite implementar una blacklist que invalide tokens específicos:
# Almacén simple para tokens revocados (en producción usar Redis o base de datos)
blacklisted_tokens = set()
@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload):
token_id = jwt_payload['jti'] # JWT ID único
return token_id in blacklisted_tokens
@app.route('/logout', methods=['POST'])
@jwt_required()
def logout():
token = get_jwt()
token_id = token['jti']
# Agregar token a la blacklist
blacklisted_tokens.add(token_id)
return jsonify({'message': 'Sesión cerrada correctamente'}), 200
Manejo de tokens en el cliente
El flujo completo desde la perspectiva del cliente implica almacenar ambos tokens y gestionar su renovación automática. Un ejemplo de implementación en JavaScript muestra este proceso:
// Ejemplo de uso desde el frontend
class AuthService {
constructor() {
this.accessToken = localStorage.getItem('access_token');
this.refreshToken = localStorage.getItem('refresh_token');
}
async makeAuthenticatedRequest(url, options = {}) {
// Intentar petición con token actual
let response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
// Si el token expiró, intentar renovarlo
if (response.status === 401) {
const refreshed = await this.refreshAccessToken();
if (refreshed) {
// Reintentar petición con nuevo token
response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.accessToken}`
}
});
}
}
return response;
}
async refreshAccessToken() {
try {
const response = await fetch('/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.refreshToken}`
}
});
if (response.ok) {
const data = await response.json();
this.accessToken = data.access_token;
localStorage.setItem('access_token', this.accessToken);
return true;
}
} catch (error) {
console.error('Error renovando token:', error);
}
return false;
}
}
Validación de tokens con información adicional
Los tokens pueden incluir metadatos útiles que permitan validaciones más granulares. La función get_jwt()
proporciona acceso completo al payload del token:
from flask_jwt_extended import get_jwt
@app.route('/admin-only', methods=['GET'])
@jwt_required()
def admin_endpoint():
claims = get_jwt()
current_user = get_jwt_identity()
# Verificar rol de administrador
if claims.get('role') != 'admin':
return jsonify({
'error': 'Acceso denegado',
'message': 'Se requieren permisos de administrador'
}), 403
return jsonify({
'message': 'Acceso concedido a área administrativa',
'user': current_user,
'permissions': claims.get('permissions', [])
}), 200
Esta implementación de tokens de acceso y refresh proporciona una base sólida para sistemas de autenticación en APIs REST, combinando seguridad robusta con una experiencia de usuario fluida.
Otras lecciones de Flask
Accede a todas las lecciones de Flask y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Flask
Introducción Y Entorno
Instalación Y Configuración Flask Con Venv
Introducción Y Entorno
Rutas Endpoints Rest Get
Api Rest
Respuestas Con Esquemas Flask Marshmallow
Api Rest
Rutas Endpoints Rest Post, Put Y Delete
Api Rest
Manejo De Errores Y Códigos De Estado Http
Api Rest
Ejercicios de programación de Flask
Evalúa tus conocimientos de esta lección Flask-JWT-Extended con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.