Autenticación Segura con API Keys para Servicios y Clientes
Cómo generar, distribuir, validar y rotar API keys para autenticación machine-to-machine usando firmas HMAC, scopes y políticas de rate limiting.
Visión general
Las API keys son la forma más simple y ampliamente desplegada de autenticación machine-to-machine. A diferencia de los flujos OAuth2 diseñados para delegación de usuarios, o los tokens JWT que codifican claims, las API keys son strings opacos intercambiados entre servicios confiables. Cuando se implementan correctamente, proporcionan autenticación rápida, revocación simple y control de acceso granular a través de permisos con scope.
El desafío con las API keys no es la generación — cualquier string aleatorio sirve — sino la gestión del ciclo de vida. Las keys deben generarse con suficiente entropía, transmitirse sobre TLS, validarse eficientemente, rotarse periódicamente y revocarse inmediatamente ante compromiso. Una API key filtrada embebida en una app móvil o subida a un repo a un repositorio público otorga a atacantes los mismos privilegios que el servicio legítimo. El siguiente enfoque cubre generación de keys con firmas HMAC, validación de requests, aplicación de scopes y estrategias de rotación.
Cuándo usarlo
Usa esta receta cuando:
- Autenticando servicios backend, microservicios o funciones serverless entre sí
- Proporcionando a desarrolladores de terceros acceso a una API pública con límites de uso
- Asegurando endpoints de webhook que reciben notificaciones push de proveedores externos
- Reemplazando autenticación básica (usuario/contraseña) en llamadas service-to-service
- Implementando acceso a API por niveles con diferentes keys para operaciones de solo lectura vs escritura
Sáltate esta receta cuando quien llama es una persona — sesiones, OAuth2 o magic links encajan mejor; una API key atada a un humano es solo una contraseña compartida que nadie rota.
Solución
Generando API Keys Seguras (Python)
import secrets
import hmac
import hashlib
from datetime import datetime, timezone
class APIKeyManager:
def __init__(self, master_secret: str):
self.master_secret = master_secret.encode()
def generate_key(self, owner_id: str, scopes: list[str]) -> dict:
random_part = secrets.token_urlsafe(32)
key_id = f"pk_{random_part[:16]}"
# Firma HMAC-SHA256 que vincula la key al owner y scopes
payload = f"{key_id}:{owner_id}:{':'.join(sorted(scopes))}"
signature = hmac.new(
self.master_secret,
payload.encode(),
hashlib.sha256
).hexdigest()[:16]
api_key = f"{key_id}.{signature}"
return {
"key_id": key_id,
"api_key": api_key,
"owner_id": owner_id,
"scopes": scopes,
"created_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"last_used": None,
}
def validate_key(self, api_key: str, owner_id: str, scopes: list[str]) -> bool:
parts = api_key.split('.')
if len(parts) != 2:
return False
key_id, provided_sig = parts
payload = f"{key_id}:{owner_id}:{':'.join(sorted(scopes))}"
expected_sig = hmac.new(
self.master_secret,
payload.encode(),
hashlib.sha256
).hexdigest()[:16]
return hmac.compare_digest(provided_sig, expected_sig)
Validando API Keys en Middleware (Node.js / Express)
const crypto = require('crypto');
function apiKeyAuth(masterSecret) {
return async (req, res, next) => {
const authHeader = req.headers['x-api-key'];
if (!authHeader) {
return res.status(401).json({ error: 'API key required' });
}
const keyData = await redis.get(`apikey:${authHeader}`);
if (!keyData) {
return res.status(401).json({ error: 'Invalid API key' });
}
const key = JSON.parse(keyData);
if (key.expires_at && new Date(key.expires_at) < new Date()) {
return res.status(401).json({ error: 'API key expired' });
}
const requiredScope = req.routeScope;
if (requiredScope && !key.scopes.includes(requiredScope)) {
return res.status(403).json({ error: 'Insufficient scope' });
}
redis.hset(`apikey:${authHeader}:meta`, 'last_used', Date.now());
req.apiKey = key;
next();
};
}
app.get('/api/v1/users', apiKeyAuth(process.env.API_SECRET), requireScope('users:read'), (req, res) => {
res.json(users);
});
Validación de Key en API Gateway (AWS)
openapi: 3.0.1
info:
title: Secure API
paths:
/users:
get:
x-amazon-apigateway-request-validator: params-only
security:
- api_key: []
x-amazon-apigateway-integration:
type: aws_proxy
uri: arn:aws:apigateway:...:lambda:path/...
components:
securitySchemes:
api_key:
type: apiKey
name: x-api-key
in: header
x-amazon-apigateway-api-key-source: HEADER
Explicación
- Estructura de key: una API key bien diseñada contiene un identificador público (key ID) y una firma secreta. El key ID se loguea y muestra en dashboards; la firma se valida server-side. Nunca almacenes la key completa en logs.
- Validación HMAC: en lugar de una fila por key y una consulta por petición, puedes validar keys con HMAC. La firma prueba que tú emitiste la key — sin ir a la base de datos. Eso sí, los metadatos (propietario, scopes, expiración) siguen necesitando una consulta.
- Control de acceso basado en scopes: asigna scopes como
users:read,orders:write,admin:fulla cada key. El middleware comprueba que el scope que pide el endpoint está en la lista de la key antes de dejar pasar. - Rate limiting por key: cuenta peticiones por API key en Redis con ventanas TTL. Aplica límites por nivel — una key gratuita puede tener 100 peticiones/hora y una enterprise 100,000.
Guardar keys como hashes
Una tabla de keys en texto plano convierte cualquier brecha en un regalo de credenciales. Hashea en reposo y compara digests en cada consulta — la key cruda solo existe en la petición del cliente:
import hashlib
def key_hash(api_key: str) -> str:
return hashlib.sha256(api_key.encode()).hexdigest()
# Al emitir: guarda key_hash(api_key) en Redis/DB, nunca la key
# En la petición: busca redis.get(f"apikey:{key_hash(incoming)}")
Mantén una columna aparte con el prefijo key_id para poder loguear pk_abc123 sin exponer tampoco el digest.
Rotar keys sin downtime
La rotación funciona cuando ambas keys validan durante una ventana de gracia:
- Emite
key_newal propietario y registrakey_old: {valid_until: now + 7d}. - El middleware acepta cualquiera de las dos hasta
valid_until; loguea cuál usó cada petición. - Pasada la ventana, borra
key_old— los rezagados reciben un 401 limpio con link de renovación endocumentation_url.
Automatiza el ciclo entero: un cron que marca keys con más de 90 días y avisa al propietario vale más que cualquier documento de política.
Variantes
| Enfoque | Almacenamiento | Velocidad validación | Revocación | Mejor para |
|---|---|---|---|---|
| HMAC stateless | Ninguno (firma) | Rápida (sin DB) | Imposible | Servicios internos con keys de corta duración |
| Database lookup | SQL/NoSQL | Media | Instantánea | APIs públicas con niveles de usuario |
| Redis cache | Redis | Rápida | Basada en TTL | APIs de alto tráfico |
| API Gateway managed | Cloud provider | Rápida | Vía dashboard | APIs alojadas en AWS/GCP/Azure |
Lo que funciona
- Nunca commitees keys a control de versiones: gitignore
para archivos. env` y ejecuta herramientas de escaneo de secretos (GitGuardian, TruffleHog) en pipelines CI. Rota inmediatamente cualquier key encontrada en historial de commits. - Usa HTTPS exclusivamente: las API keys enviadas sobre HTTP sin encriptar son trivialmente interceptadas por interceptores de red. Rechaza peticiones HTTP planas en el load balancer o gateway.
- Rota keys proactivamente: establece una edad máxima de key (90 días para producción, 30 días para alta sensibilidad) y notifica a propietarios antes de expiración. Provee un período de gracia donde tanto la key vieja como la nueva funcionan.
- Loguea key IDs, nunca keys completas: al loguear requests, registra solo el prefijo del key ID (
pk_abc123...). La porción de firma completa nunca debería aparecer en logs, mensajes de error o URLs. - Implementa alertas de uso: notifica a propietarios de keys cuando se acercan al 80% de su límite de rate. Esto reduce sorpresas de error 429 y motiva mejoras de plan para crecimiento legítimo.
Errores comunes
- Usar formatos de key predecibles: IDs secuenciales o keys UUIDv1 filtran el momento de generación. token_urlsafe
o/dev/urandom`). - Almacenar keys en código client-side: el navegador y las apps móviles no pueden guardar secretos — todo lo que se empaqueta es público.
- No validar scopes: una key de solo lectura de analytics no debería poder eliminar registros. Comprueba los scopes en el propio endpoint, no solo al autenticar.
- Hardcodear keys en configuración: json` o variables de entorno en servidores compartidos las expone a todos los procesos.
Consideraciones de Seguridad
- Keys en URLs: un
?api_key=en el query param queda en logs de servidor, historial del navegador y headers Referer. Envía keys solo por headersX-API-KeyoAuthorization. - Keys en código del cliente: lo que se envía a un navegador o app móvil es público. Solo server-to-server; usa un proxy backend si el cliente necesita los datos.
- Keys sin cifrar en reposo: una brecha de base de datos no debería entregar keys utilizables. Guarda
sha256(key)y compara hashes en cada consulta — el plaintext nunca descansa. - Sin política de rotación: las keys que nunca expiran quedan comprometidas para siempre. Rota con calendario (90 días es un valor por defecto razonable) y avisa a los dueños antes del vencimiento.
- Fugas de timing en validación: comparar keys con
==filtra la longitud del prefijo vía timing de respuesta. Usa comparación de tiempo constante —hmac.compare_digesten Python,crypto.timingSafeEqualen Node. - Sin rate limiting en endpoints de auth: intentos ilimitados significan fuerza bruta de prefijos. Limita por IP y por key, y loguea los fallos para revisión.
- Fallos silenciosos: loguea cada intento de key rechazado con IP y timestamp — una ráfaga de 401 suele ser fuerza bruta o una key filtrada.
Preguntas frecuentes
¿Cuál es la diferencia entre API keys y tokens JWT?
Las API keys son strings opacos para auth service-to-service — el servidor las consulta para saber qué puede hacer el llamante. Los JWT llevan sus claims dentro del propio token y encajan mejor en sesiones de usuario: identidad, expiración y permisos viajan sin consulta. Las keys dicen "qué app"; los JWT pueden decir "qué usuario".
¿Cómo revoco una API key comprometida?
Con keys respaldadas por base de datos, elimina o deshabilita el registro — deja de funcionar en el próximo request. Las keys HMAC stateless no se pueden revocar individualmente: o rotas el master secret (lo que mata todas las keys) o mantienes una lista de bloqueo de IDs comprometidos y la compruebas en cada llamada.
¿Debería encriptar las API keys en reposo?
Sí — guarda el hash, no la key. Cuando llega una key, la calculas su hash y comparas contra el hash almacenado. Una base de datos filtrada entonces entrega digests que los atacantes no pueden reutilizar, no credenciales vivas.
¿Puedo usar API keys para autenticación de usuarios?
No encaja. Las keys sirven para clientes máquina; los humanos necesitan sesiones, OAuth2 u OIDC — cosas que soportan MFA y recuperación self-service. Una API key atada a una persona se convierte en una contraseña compartida que nadie rota.
Recursos Relacionados
Autenticación JWT
Firma, verifica y refresca JWTs en Python, JavaScript y Java — flujos access/refresh, pares de claves RS256 y revocación de tokens bien hecha.
RecipeInicio de Sesión OAuth 2.0
Cómo implementar autenticación OAuth 2.0 con Google, GitHub y otros proveedores. Cubre el flujo Authorization Code con PKCE, validación de state y refresh tokens.
RecipeLimitación de Tasa (Rate Limiting)
Cómo implementar rate limiting en APIs usando token bucket, sliding window y fixed window en Python, JavaScript y Java.
RecipeGestionar Secretos de Aplicaciones de Forma Segura
Cómo almacenar, rotar e inyectar API keys, contraseñas de base de datos y certificados sin hardcodearlos en código fuente o archivos de entorno.
RecipeImplementar Login Sin Contraseña con Magic Links
Cómo construir autenticación passwordless segura usando links mágicos de tiempo limitado enviados por email, con generación de tokens, validación y prevención de ataques replay.
RecipeFirma HMAC para autenticación de API (AWS Sig v4)
Asegurá peticiones de API con firmas HMAC y autenticación AWS Signature v4 para integridad de mensajes a prueba de manipulaciones.