En este artículo, explicamos en detalle cómo utilizar JWT (JSON Web Token) para autenticación y mejorar la seguridad en Python, desde su generación hasta su verificación. Utilizando bibliotecas de Python, cubrimos conceptos básicos, ejemplos de implementación y medidas de seguridad para que puedas comprender completamente el proceso de autenticación con JWT. Profundiza en los conocimientos necesarios para construir funciones de autenticación confiables en aplicaciones web o el desarrollo de API.
Conceptos básicos y funcionamiento de JWT
JWT (JSON Web Token) es uno de los formatos de token más utilizados en la autenticación web. Se basa en un estándar (RFC 7519) para transmitir información de forma segura entre el cliente y el servidor, y tiene una función de prevención de alteraciones mediante firmas.
Estructura de JWT
JWT se compone de tres partes separadas por puntos (.
) de la siguiente manera:
- Header (Encabezado)
- Especifica el tipo de token (por ejemplo, JWT) y el algoritmo de firma (por ejemplo, HS256).
{
"alg": "HS256",
"typ": "JWT"
}
- Payload (Carga útil)
- Contiene los datos (claims) que se incluyen en el token en formato JSON.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
- Signature (Firma)
- Firma generada a partir del encabezado y la carga útil usando una clave secreta o pública. Su función es prevenir alteraciones.
Principio de funcionamiento de JWT
- Cuando el cliente envía las credenciales al iniciar sesión, el servidor genera el JWT basado en esa información.
- El JWT generado es enviado al cliente, y generalmente se almacena en cookies o cabeceras HTTP.
- El cliente envía el JWT al servidor con cada solicitud, y el servidor verifica el token antes de permitir la solicitud.
Este proceso permite una autenticación sin estado, mejorando la escalabilidad y la seguridad.
Opciones de bibliotecas JWT en Python
Existen varias bibliotecas útiles para manejar JWT en Python. Cada una tiene características propias, por lo que es importante elegir la adecuada según los requisitos del proyecto.
Bibliotecas principales
- PyJWT
- La biblioteca más común y liviana, que permite generar y verificar JWT de manera sencilla.
- Sitio oficial: PyJWT GitHub
- Características:
- API sencilla y fácil de usar
- Soporta una amplia variedad de algoritmos de firma como HMAC (HS256, HS512) y RSA (RS256, RS512)
- Instalación:
bash pip install pyjwt
- Soporta JSON Web Token, JSON Web Signature (JWS) y JSON Web Encryption (JWE).
- Características:
- Amplio soporte para cifrado (JWE)
- Gran capacidad de personalización
- Instalación:
bash pip install python-jose
- Para autenticación simple: PyJWT es ideal, liviano y fácil de usar.
- Para flujos de autenticación avanzados: Si utilizas OAuth o OpenID Connect, Authlib es la opción recomendada.
- Para necesidades de cifrado: Si se requiere cifrado de tokens, python-jose es la mejor opción.
- Configuración de la clave secreta
- Establecemos la clave secreta que se usará para firmar el token. Esta debe ser gestionada de forma segura en el servidor.
- Creación del payload
- Incluimos claims como
sub
yname
. - También podemos establecer
iat
(hora de emisión) yexp
(fecha de expiración). - Generación del token
- Utilizamos
jwt.encode
para codificar el payload y generar el token. - Los algoritmos de firma más comunes son
HS256
yRS256
. - Gestión de la clave secreta
- La clave secreta debe ser gestionada de manera estricta y no debe ser filtrada a terceros.
- Configuración de la expiración
- Establecer
exp
es esencial para limitar el tiempo de validez del token desde el punto de vista de seguridad. - Tamaño del token
- Incluir demasiada información en el payload puede aumentar el tamaño del token, afectando la eficiencia de la comunicación.
- Claims registrados
- Claims estandarizados según la especificación, que tienen significados específicos.
- Ejemplos principales:
iss
(emisor): Indica la entidad que emitió el token.sub
(tema): Indica el sujeto del token (por ejemplo, ID del usuario).aud
(receptor): Indica el destinatario del token (por ejemplo, nombre del servicio).exp
(expiración): Fecha de expiración del token en formato de marca de tiempo UNIX.iat
(fecha de emisión): Fecha de emisión del token en formato de marca de tiempo UNIX.
- Claims públicos
- Datos específicos de la aplicación, con nombre de espacio para evitar colisiones con otros sistemas.
- Claims privados
- Datos que solo se usan dentro de la aplicación, sin necesidad de un espacio de nombres.
- Contraseñas
- Información de tarjetas de crédito
- Información personal identificable (PII)
- Aumento del tamaño de la solicitud HTTP
- Aumento del uso de datos en dispositivos móviles
- Firma simétrica (HMAC)
- Utiliza una clave secreta compartida para la firma y la verificación.
- Es simple y rápida, pero la gestión de las claves puede ser un desafío.
- Algoritmos de ejemplo: HS256, HS512
- Firma asimétrica (RSA, ECDSA)
- Firma con una clave secreta y verificación con una clave pública.
- Es adecuada para comunicaciones entre servidores o interacciones con terceros.
- Algoritmos de ejemplo: RS256, ES256
- La clave secreta se mantiene segura y la clave pública puede ser distribuida sin comprometer la seguridad.
- Es útil cuando hay varios validadores de tokens.
- HS256 (HMAC): Ideal para sistemas de autenticación simples del lado del servidor.
- RS256 (RSA): Ideal para comunicaciones entre servidores o integración con sistemas externos.
- ES256 (ECDSA): Eficiente para firmas con claves públicas más ligeras.
- Gestión rigurosa de claves secretas
- Protege estrictamente las claves secretas y controla el acceso.
- Configuración de la expiración
- Utiliza el claim
exp
para establecer un período de validez corto para los tokens. - Especificación del algoritmo de firma
- Especifica siempre el algoritmo de firma de forma explícita y evita usar los valores predeterminados.
Puntos clave para elegir una biblioteca
Dependiendo de la escala de tu proyecto y los requisitos de seguridad, puedes elegir entre estas bibliotecas para utilizar JWT de manera eficiente.
Generación de JWT en Python
Para generar JWT, puedes aprovechar las bibliotecas de Python de manera eficiente. A continuación, explicamos el proceso básico de generación de JWT usando la biblioteca PyJWT.
Generación básica de JWT con PyJWT
El siguiente código muestra un ejemplo de cómo generar JWT utilizando PyJWT.
import jwt
import datetime
# Configuración de la clave secreta
SECRET_KEY = "your-secret-key"
# Creación del payload
payload = {
"sub": "1234567890", # Identificador del usuario
"name": "John Doe", # Información del usuario
"iat": datetime.datetime.utcnow(), # Hora de emisión del token
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1) # Fecha de expiración (1 hora)
}
# Generación del token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("JWT generado:", token)
Explicación del código
Generación de JWT con firma RSA
A continuación, te mostramos un ejemplo usando claves públicas y privadas para firmar el JWT.
import jwt
# Clave privada y pública RSA
private_key = """-----BEGIN RSA PRIVATE KEY-----
...(contenido de la clave privada)...
-----END RSA PRIVATE KEY-----"""
public_key = """-----BEGIN PUBLIC KEY-----
...(contenido de la clave pública)...
-----END PUBLIC KEY-----"""
# Creación del payload
payload = {
"sub": "1234567890",
"name": "John Doe",
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# Generación del token con firma RSA
token = jwt.encode(payload, private_key, algorithm="RS256")
print("JWT generado con RSA:", token)
Puntos importantes
Utiliza estos ejemplos de código para generar JWT según lo necesario y construir sistemas de autenticación seguros.
Diseño de los datos en el payload de JWT
El payload de JWT contiene información sobre el usuario o los atributos del token. Un diseño adecuado del payload es clave, ya que influye en el tamaño del token y en la seguridad.
Estructura básica del payload
El payload de JWT puede incluir tres tipos de claims (reclamaciones):
Mejores prácticas de diseño
Incluir solo los datos mínimos necesarios
El tamaño del JWT afecta la eficiencia de la comunicación. Incluye solo los datos necesarios para la autenticación y autorización.
{
"sub": "1234567890",
"name": "John Doe",
"roles": ["admin", "user"]
}
Tener en cuenta la sensibilidad de los datos
JWT está firmado, pero no cifrado. Evita incluir información sensible como contraseñas o datos personales identificables.
Configurar claramente la expiración del token
Asegúrate de incluir exp
para definir claramente el tiempo de expiración del token y reducir riesgos de seguridad.
Ejemplo de diseño de payload de JWT
A continuación, un ejemplo práctico de un payload de JWT:
{
"iss": "https://example.com", // Emisor
"sub": "user123", // ID del usuario
"aud": "https://myapi.example.com", // Receptor
"exp": 1701209952, // Expiración (timestamp UNIX)
"iat": 1701206352, // Fecha de emisión
"roles": ["user", "admin"], // Permisos del usuario
"preferences": {
"theme": "dark", // Atributos personalizados
"notifications": true
}
}
Consideraciones sobre el tamaño del token
Un payload grande puede aumentar el tamaño total del token, lo que puede causar:
Para mantener el token ligero, reduce los claims personalizados según sea necesario.
Resumen
El diseño del payload es clave para mantener la eficiencia y seguridad del token. Utilizando claims estándar y limitando los datos incluidos, podemos lograr un diseño óptimo.
Firma utilizando claves secretas y públicas
La firma juega un papel crucial en asegurar la seguridad de JWT. Especialmente, la firma utilizando claves públicas y secretas (firma asimétrica) es ideal para evitar alteraciones y asegurar la fiabilidad del token. A continuación, explicamos cómo funciona la firma y cómo implementarla en Python.
Funcionamiento de la firma
Existen dos tipos de firmas en JWT:
Ventajas de la firma asimétrica:
Implementación de firma RSA en Python
Veamos un ejemplo de cómo generar y verificar JWT utilizando la firma asimétrica en Python usando la biblioteca PyJWT.
Preparación de las claves de firma
Genera previamente las claves pública y privada.
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
Generación de JWT
Genera el JWT usando la clave secreta.
import jwt
import datetime
# Carga de la clave secreta
with open("private.pem", "r") as key_file:
private_key = key_file.read()
# Creación del payload
payload = {
"sub": "1234567890",
"name": "John Doe",
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# Generación del JWT (con firma)
token = jwt.encode(payload, private_key, algorithm="RS256")
print("JWT generado:", token)
Verificación del JWT
Verifica el JWT usando la clave pública.
# Carga de la clave pública
with open("public.pem", "r") as key_file:
public_key = key_file.read()
# Verificación del JWT
try:
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
print("Payload decodificado:", decoded)
except jwt.ExpiredSignatureError:
print("El token ha expirado.")
except jwt.InvalidTokenError:
print("El token es inválido.")
Selección de algoritmo de firma
Mejores prácticas
Resumen
El uso de firmas con claves públicas y secretas es fundamental para lograr una seguridad sólida en JWT. La firma RSA es particularmente útil para comunicaciones entre servidores o sistemas que requieren la distribución de claves públicas. Utilizando ejemplos prácticos de Python, puedes crear sistemas de autenticación seguros y confiables.
Proceso de verificación de JWT
La verificación de JWT es esencial para garantizar que el token no haya sido alterado, que esté firmado con la clave correcta y que esté dentro de su período de validez. A continuación, explicamos los pasos de verificación y ejemplos de implementación en Python.
- python-jose
- python-jose
- Authlib
Proceso básico de verificación de JWT
- Verificación de la firma
- Verifique si el token está firmado con la clave secreta o pública correcta. Si la firma no coincide, el token es inválido.
- Verificación de la validez
- Verifique la reclamación
exp
en la carga útil para asegurarse de que el token esté dentro de su periodo de validez. Un token expirado es inválido.
- Verificación de otras reclamaciones
- Si es necesario, verifique las reclamaciones como
iss
(emisor) oaud
(audiencia). Esto garantiza que el token solo se use en el sistema adecuado.
Verificación de JWT con Python
A continuación se muestra un ejemplo de cómo verificar un JWT utilizando la biblioteca PyJWT.
Verificación básica
import jwt
# Especificar la clave secreta o pública
SECRET_KEY = "your-secret-key"
# Token a verificar
token = "your.jwt.token"
# Decodificar y verificar el JWT
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
print("Carga útil verificada:", decoded)
except jwt.ExpiredSignatureError:
print("El token ha expirado.")
except jwt.InvalidTokenError:
print("El token es inválido.")
Verificación con RS256 (clave pública)
Si se usa firma asimétrica (RS256), se verifica la firma utilizando la clave pública.
# Cargar la clave pública
with open("public.pem", "r") as key_file:
public_key = key_file.read()
# Decodificar y verificar el JWT
try:
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
print("Carga útil verificada:", decoded)
except jwt.ExpiredSignatureError:
print("El token ha expirado.")
except jwt.InvalidTokenError:
print("El token es inválido.")
Verificación de reclamaciones adicionales durante la verificación
Puede especificar reclamaciones para reforzar la verificación como se muestra a continuación:
decoded = jwt.decode(
token,
SECRET_KEY,
algorithms=["HS256"],
options={"verify_exp": True}, # Habilitar la verificación de expiración
audience="https://myapi.example.com", # Verificar la audiencia
issuer="https://example.com" # Verificar el emisor
)
Consideraciones durante la verificación
- Especificación del algoritmo
- Siempre especifique el algoritmo que se va a utilizar durante la verificación. Si se usa
None
o un algoritmo inesperado, los riesgos de seguridad aumentan.
- Manejo de tokens expirados
- Para los tokens expirados (
ExpiredSignatureError
), se debe guiar al usuario a que vuelva a iniciar sesión o a refrescar el token.
- Verificación del origen del token
- Verifique que el token provenga de una fuente confiable.
Diagrama general del flujo de verificación de tokens
- Recibir el token
- Verificar la estructura del token (Header, Payload, Signature)
- Verificar el algoritmo en el encabezado y verificar la firma
- Verificar
exp
eiss
en la carga útil - Aceptar la solicitud solo si la verificación es exitosa
Resumen
La verificación de JWT es fundamental en el proceso de autenticación. Al verificar la firma con la clave correcta y comprobar la validez del token, podemos construir un sistema seguro y confiable. Siguiendo los ejemplos de implementación en Python, podemos integrar un proceso de verificación seguro para JWT.
Implementación de la validez del token y refresco
Al usar JWT, la configuración de la validez del token y la introducción de un token de refresco son esenciales para fortalecer la seguridad. En esta sección, explicamos cómo configurar la validez del token y cómo implementar el refresco del token utilizando Python.
Configuración de la validez
Usamos la reclamación exp
(expiration) de JWT para configurar la validez del token. A continuación se muestra un ejemplo de implementación en Python.
import jwt
import datetime
# Configurar la clave secreta
SECRET_KEY = "your-secret-key"
# Agregar la validez al payload
payload = {
"sub": "1234567890",
# ID del usuario
"name": "John Doe", # Información del usuario
"iat": datetime.datetime.utcnow(), # Tiempo de emisión
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # Válido por 30 minutos
}
# Generar el token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("Access Token:", token)
Consideraciones
- Configurar la validez por un período corto (de 5 a 30 minutos) ayuda a reducir los riesgos en caso de robo del token.
- Si el token ha expirado, se debe pedir al usuario que vuelva a autenticarse o que actualice su token.
Diseño del token de refresco
El token de refresco se emite por separado del token de acceso y se utiliza para actualizar un token de acceso expirado. Características del token de refresco:
- Tiene una validez más larga (de varios días a semanas)
- Suele ser gestionado y almacenado en el servidor
Ejemplo de emisión del token de refresco
# Payload del token de refresco
refresh_payload = {
"sub": "1234567890", # ID del usuario
"iat": datetime.datetime.utcnow(), # Tiempo de emisión
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=7) # Válido por 7 días
}
# Generar el token de refresco
refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm="HS256")
print("Refresh Token:", refresh_token)
Implementación del proceso de refresco
Cuando el token ha expirado, se usa el token de refresco para generar un nuevo token de acceso.
# Decodificar el token de refresco y generar un nuevo token de acceso
try:
decoded_refresh = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
# Generar un nuevo token de acceso
new_access_payload = {
"sub": decoded_refresh["sub"],
"name": "John Doe",
"iat": datetime.datetime.utcnow(),
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
new_access_token = jwt.encode(new_access_payload, SECRET_KEY, algorithm="HS256")
print("New Access Token:", new_access_token)
except jwt.ExpiredSignatureError:
print("El token de refresco ha expirado.")
except jwt.InvalidTokenError:
print("El token de refresco es inválido.")
Consideraciones de seguridad para el token de refresco
- Gestión en el servidor
- Los tokens de refresco deben almacenarse en el servidor (base de datos o memoria) para prevenir el robo o la manipulación.
- Uso único
- Los tokens de refresco usados deben ser invalidados para evitar el secuestro de sesión.
- Reautenticación al usarlos
- Requiera reautenticación cuando se use un token de refresco para aumentar la seguridad.
Ejemplo de diseño de API para el token de refresco
Ejemplo de diseño de una API para actualizar el token:
- El cliente envía el token de acceso expirado junto con el token de refresco.
- El servidor verifica el token de refresco.
- Si es válido, el servidor emite un nuevo token de acceso y lo devuelve al cliente.
Ejemplo de punto de acceso API
POST /api/token/refresh
Authorization: Bearer {refresh_token}
Ejemplo de respuesta:
{
"access_token": "new_access_token",
"expires_in": 1800
}
Resumen
Al implementar correctamente la validez del token JWT y el uso de tokens de refresco, se puede asegurar la seguridad mientras se mejora la experiencia del usuario. Combinando una validez corta y el uso de tokens de refresco, se puede construir un sistema de autenticación confiable.
Riesgos de seguridad y sus contramedidas
JWT es una herramienta útil para la autenticación y gestión de sesiones, pero existen riesgos de seguridad derivados de una implementación o gestión incorrecta. En esta sección, explicamos los riesgos comunes relacionados con JWT y sus soluciones.
Riesgos de seguridad comunes
1. Fuga de claves secretas
Si se filtra la clave de firma de JWT, un atacante podría generar tokens fraudulentos o tomar el control del sistema.
Contramedidas:
- Guarde las claves secretas en un entorno seguro (por ejemplo, variables de entorno o servicios de gestión de secretos).
- Rote las claves regularmente.
- Verifique siempre la firma del token.
- Especifique explícitamente el algoritmo y no permita algoritmos no seguros como
none
. - Use HTTPS para cifrar la comunicación.
- Almacene los tokens en un almacenamiento seguro (por ejemplo, cookies con atributo HttpOnly).
- Establezca una validez corta para los tokens.
- Asigne un identificador único (
jti
) a cada token y realice un seguimiento del historial de uso del token en el servidor. - Registre los tokens usados en una lista negra.
- Combine los tokens de refresco con una lista negra para permitir la invalidación.
- Configure una validez corta para los tokens y actualícelos con frecuencia.
- Prevención de XSS (Cross-Site Scripting)
Proteja los tokens usando cookies HttpOnly. - Monitoreo de logs
Registre los logs de emisión y verificación de tokens para detectar usos sospechosos.
2. Manipulación de tokens
Si la firma no se verifica correctamente, es posible que los tokens sean manipulados.
Contramedidas:
3. Robo de tokens
Si un token es robado, un atacante puede actuar como un usuario legítimo.
Contramedidas:
4. Ataques de repetición de tokens
Un token robado puede ser reutilizado, lo que permite al atacante secuestrar la sesión.
Contramedidas:
5. Dificultad para invalidar tokens
JWT es sin estado, lo que significa que no existe un mecanismo para invalidar un token una vez que ha sido emitido.
Contramedidas:
Implementación de contramedidas específicas en Python
1. Gestión de claves
Ejemplo de obtención de la clave secreta desde las variables de entorno:
import os
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
if not SECRET_KEY:
raise ValueError("¡La clave secreta debe estar configurada!")
2. Especificación de algoritmo
Para evitar algoritmos no seguros, especifique el algoritmo a utilizar:
import jwt
# Verificar el token
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
3. Uso de HTTPS
Forzar el uso de HTTPS en Flask, por ejemplo:
from flask import Flask
app = Flask(__name__)
app.config['PREFERRED_URL_SCHEME'] = 'https'
4. Prevención de ataques de repetición
Configurar un identificador único (jti
) y registrar el uso en la base de datos:
import uuid
payload = {
"sub": "1234567890",
"jti": str(uuid.uuid4()), # ID único
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
5. Implementación de lista negra
Ejemplo de invalidación de token utilizando Redis:
import redis
# Cliente de Redis
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
# Invalidar el token
redis_client.setex("blacklist:" + token, 1800, "true") # Invalidado en 1800 segundos
Otras recomendaciones
Resumen
JWT ofrece muchos beneficios, pero si no se toman las medidas de seguridad adecuadas, puede dar lugar a riesgos graves. Al adoptar las contramedidas que aquí se presentan, como la correcta gestión de claves secretas, la verificación de firmas y la reducción de la validez de los tokens, podemos construir un sistema de autenticación seguro.
Resumen
Este artículo cubrió desde los conceptos básicos hasta la implementación y medidas de seguridad para la generación y verificación de JWT utilizando Python.
JWT es una herramienta útil para construir sistemas de autenticación sin estado y escalables, pero se deben tener en cuenta aspectos de seguridad como la gestión de claves, la validez de los tokens y el diseño de los tokens de refresco.
Utilizando bibliotecas de Python, podemos construir sistemas eficientes y seguros que ofrezcan una autenticación confiable y una gestión de sesiones segura.