API Security

API Keys

Una API key es una credencial de larga duración para máquinas. Emítela una sola vez, almacena únicamente un hash, asígnale un scope restringido y establece un método para rotarla y revocarla antes de que sea necesario.

intermediate14 min readUpdated 16 sept 2026
keys.ts
ts
// keys.ts
import crypto from "node:crypto";

export function generateApiKey(env: "live" | "test") {
  const secret = crypto.randomBytes(32).toString("base64url");
  const prefix = `sk_${env}_`;
  const key = `${prefix}${secret}`;

  return {
    key,                        // returned to the user once
    prefix: key.slice(0, 12),   // stored and indexed for lookup
    hash: hashKey(key),         // stored instead of the key
  };
}

export function hashKey(key: string): string {
  return crypto.createHash("sha256").update(key).digest("hex");
}

export function timingSafeEqual(a: string, b: string): boolean {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}
Uso
APIs públicas y comunicación server-to-server
Formato
Prefijo más un secreto aleatorio
Almacenamiento
Hash SHA-256
Visibilidad
Una sola vez, al crearla
Transporte
Cabecera Authorization
Scoping
Permisos y rate limits
Rotación
Crear la nueva clave y luego revocar la antigua
Riesgo de filtración
Historial de Git, logs, código del cliente

Por que importa

Lo que ofrece un buen sistema de claves

Una credencial, muchos servicios

Una clave autentica una máquina sin necesidad de un flujo de login. Es sencilla de emitir, enviar y rotar, razón por la cual todas las plataformas para desarrolladores las utilizan.

Hashed en reposo

Almacena solo un hash, exactamente como harías con una contraseña. Así, un volcado de base de datos robado no proporcionará nada que un atacante pueda enviar a tu API.

Con scope y rate limit

Cada clave lleva sus propios permisos y cuotas, por lo que una clave comprometida solo puede hacer aquello que tenía permitido y a la velocidad autorizada.

La imagen completa

Tres propiedades de una clave segura

Un secreto aleatorio que no se puede adivinar, un hash en reposo que no se puede reutilizar desde un volcado de datos y un scope que limite el daño en caso de filtración.

Secreto

Identificar

Una cadena aleatoria de alta entropía es la credencial en sí. Su única función es ser imposible de adivinar y única para cada cliente.

Scope

Limitar

Una clave lleva un conjunto de permisos. El verificador comprueba la acción contra el scope antes de que se ejecute el handler, exactamente como los roles de un usuario.

Cuota

Proteger

Las claves crean un contenedor natural para los rate limits, evitando que una integración ruidosa agote la capacidad para los demás.

HTML5 de un vistazo

Las piezas que construirás

Formato

Un prefijo legible más un secreto aleatorio largo, ej. sk_live_8f2a...

Hashing

Almacena un hash SHA-256; compara mediante una función de tiempo constante.

Scopes

Permisos granulares como projects:read y deploy:write.

Rate limits

Asigna una cuota a cada clave y aplícala individualmente.

Rotación

Emite un reemplazo, migra el tráfico y luego revoca la clave antigua.

Monitoreo

Rastrea last_used_at y genera alertas ante cambios bruscos de comportamiento.

Modelo de datos

La tabla api_keys

Solo se almacenan un hash y un prefijo corto. La clave completa existe una sola vez, en la respuesta que la creó, y nunca puede ser recuperada.

La tabla api_keysPostgreSQL table
  • idbigserialClave primaria subrogada
  • nametextEtiqueta humana como 'CI deploy' o 'mobile app'
  • prefixtextCaracteres iniciales de la clave, indexados para búsqueda rápida
  • key_hashtextSHA-256 de la clave completa, nunca la clave en sí
  • scopestext[]Permisos que la clave puede ejercer
  • owner_idbigintEl usuario o servicio que creó la clave
  • last_used_attimestamptzActualizado al usarla para detección de anomalías y limpieza
  • revoked_attimestamptzEstablecido cuando la clave es revocada; null significa activa

Solo se almacenan un hash y un prefijo corto. La clave completa existe una sola vez, en la respuesta que la creó, y nunca puede ser recuperada.

Flujo

Emisión y verificación de una clave

La clave completa existe en una única respuesta; todo lo posterior funciona mediante un hash y un prefijo.

  1. 1

    Generar un secreto aleatorio

    Obtén al menos 128 bits de un CSPRNG y combínalos con un prefijo legible.

  2. 2

    Mostrarla una sola vez

    Devuelve la clave completa en la respuesta de creación y nunca la vuelvas a almacenar ni a mostrar.

  3. 3

    Almacenar un hash y un prefijo

    Persiste el hash, el prefijo corto, los scopes y el propietario en la tabla api_keys.

  4. 4

    Enviarla en una cabecera

    El cliente presenta la clave en Authorization o en una cabecera dedicada en cada solicitud.

  5. 5

    Hashear y comparar

    Busca la clave por prefijo, hashea el valor presentado y compara en tiempo constante.

  6. 6

    Adjuntar los scopes

    Carga los permisos y la cuota de la clave en la solicitud para que el handler los aplique.

  7. 7

    Rotar o revocar

    Emite un reemplazo, migra el tráfico y marca la clave antigua como revocada.

La guia completa

API Keys: Todo lo que necesitas saber

Qué es una API key

Una API key es una cadena secreta de larga duración que identifica a una aplicación en lugar de a una persona. El cliente la envía con cada solicitud, el servidor la reconoce y se concede el acceso según los permisos asignados a dicha clave. Esa es la idea fundamental.

Es una credencial deliberadamente simple. No hay inicio de sesión, ni pantalla de consentimiento, ni intercambio de tokens. Un desarrollador se registra, crea una clave, la pega en un archivo de configuración y su código comienza a funcionar. Esa baja fricción es la razón por la cual casi todas las plataformas para desarrolladores —pagos, mapas, correo electrónico, infraestructura— distribuyen claves.

Pero que sea simple no significa que se deba ser descuidado. Una clave es una credencial de portador (bearer credential): quien la posea puede usarla, exactamente igual que el dinero en efectivo. No hay un segundo factor ni una firma. Esto significa que la seguridad de todo el sistema depende de qué tan bien generes las claves, con qué cuidado las almacenes, qué tan restringido sea su alcance y qué tan rápido puedas revocar una que se haya filtrado. Esta guía trata sobre cómo hacer esas cuatro cosas correctamente.

Cuándo usar una clave es la herramienta adecuada

Las claves no son la solución para todos los problemas de autenticación, y utilizarlas en el lugar equivocado genera riesgos reales.

Utiliza una API key cuando un servidor se comunique con otro servidor, cuando quien realiza la llamada sea una aplicación en la que confíes para manejar un secreto de larga duración, o cuando estés ofreciendo una API pública para desarrolladores. Los pipelines de CI, las integraciones de backend, los agentes de monitoreo y los servicios de terceros son casos de uso ideales. La clave se almacena en un gestor de secretos o en una variable de entorno, y nunca llega a tocar un navegador.

Utiliza OAuth cuando necesites actuar en nombre de un usuario. Si tu integración debe leer el calendario de alguien o enviar correos electrónicos en su nombre, necesitas un flujo de consentimiento y un token que represente esa delegación. Una clave no puede expresar “estos son los datos de Alice y Alice estuvo de acuerdo”.

Utiliza un JWT cuando necesites un token verificable de corta duración que contenga claims y que pueda ser validado sin necesidad de una base de datos compartida. La autenticación entre servicios en una malla (mesh) o una URL de descarga firmada son buenos ejemplos.

El caso peligroso es el cliente público. Una clave embebida en una aplicación móvil, un binario de escritorio o un bundle de navegador no es secreta: cualquiera puede extraerla. Si tu producto necesita que esos clientes llamen a tu API, coloca un proxy de backend delante o emite tokens de corta duración desde tu propio servidor después de autenticar al usuario. Nunca distribuyas una clave de larga duración dentro del código del cliente.

La estructura de una buena clave

Una buena clave es impredecible y autodescriptiva. Consta de dos partes: un prefijo corto y legible, y un secreto largo y aleatorio.

The shape of an API key
sk_live_8f2a9c1d4e6b7a0f3c5d8e2b1a4f7c9d6e3b0a8f5c2d1e4b7a9c6f0d3e8b1a4
prefixenvironment and type, safe to log and scan for
secret256 bits of cryptographically secure randomness

El prefijo cumple dos funciones. Identifica el entorno y el tipo de clave de un vistazo, y proporciona al servidor un identificador indexado para la búsqueda sin necesidad de almacenar el secreto. Un formato fijo y reconocible también hace que las filtraciones accidentales sean detectables por los escáneres de secretos, que pueden detectar sk_live_ en un commit y bloquearlo.

El secreto debe provenir de una fuente aleatoria criptográficamente segura, nunca de Math.random, una marca de tiempo o un UUID que revele su estructura. El mínimo son 128 bits de entropía; 256 bits es un valor predeterminado seguro y no supone ningún coste adicional. La codificación Base64url permite que la clave se pueda pegar en URLs y encabezados sin necesidad de escape.

const secret = crypto.randomBytes(32).toString("base64url");
const key = `sk_live_${secret}`;

Resiste la tentación de codificar información dentro de la clave, como el ID del usuario o una fecha de creación. Cualquier dato legible en la clave es información que un atacante puede obtener, y cualquier dato derivado de información predecible debilita la aleatoriedad. El prefijo es la única parte legible que necesitas, y no debe revelar nada sensible.

Muéstrala una vez y luego olvídala

La clave completa debe existir en un solo lugar después de su creación: la respuesta que se le devolvió al usuario. A partir de ese momento, el servidor almacena únicamente un hash y un prefijo, lo que significa que puede verificar una clave presentada, pero nunca podrá reproducirla.

Esta es la misma propiedad que el almacenamiento de contraseñas, y cambia totalmente las consecuencias de una brecha de seguridad. Si un atacante extrae la tabla api_keys, obtendrá hashes que no pueden enviarse a tu API. Sin el hashing, una sola filtración de la base de datos o la exposición de un backup entregaría todas las claves de los clientes de golpe.

La experiencia de usuario se deriva de esta restricción técnica. El dashboard muestra la clave una sola vez, con una advertencia clara de copiarla en ese momento, y posteriormente muestra solo el prefijo y metadatos como los scopes y el último uso. Si un usuario pierde una clave, debe rotarla; no puede recuperarla. Indica esto explícitamente en tu documentación para que nadie espere encontrarla más tarde.

res.status(201).json({
  id: row.id,
  name: row.name,
  prefix: row.prefix,
  key, // shown once, never retrievable again
  warning: "Store this key now. You will not be able to see it again.",
});

Almacena un hash, nunca la clave

El hashing es el control más importante en un sistema de API keys. También es el que los equipos suelen omitir con más frecuencia, generalmente porque quieren poder mostrar la clave nuevamente más adelante. No lo hagas.

Utiliza un hash criptográfico rápido como SHA-256. A diferencia de las contraseñas, las claves tienen entropía completa, por lo que no hay un diccionario para atacar y no es necesario un KDF lento. El hash rápido también mantiene el costo de verificación bajo en la ruta crítica.

function hashKey(key: string): string {
  return crypto.createHash("sha256").update(key).digest("hex");
}

La comparación debe ser en tiempo constante. Un === ingenuo sobre strings se interrumpe en el primer byte diferente, lo que filtra cuánto de una clave adivinada era correcto. Por lo general, esto es una preocupación teórica a través de una red, pero es trivial de evitar y es una buena práctica de higiene. Compara buffers de igual longitud con crypto.timingSafeEqual.

const a = Buffer.from(hashKey(presented));
const b = Buffer.from(record.keyHash);
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);

Si quieres ocultar el hash por completo de la base de datos, un hash con clave mediante un pepper en el servidor añade un segundo secreto que un atacante también debería obtener. Esto es opcional para sistemas de alta seguridad, pero hacer hashing no es opcional.

Búsqueda de claves sin escaneo completo

El hashing presenta un problema práctico: no puedes consultar la base de datos mediante el hash a menos que primero apliques el hash a la clave entrante, y no puedes aplicar el hash a la clave entrante hasta que sepas contra qué fila compararla. Aplicar el hash a cada fila en cada solicitud no es una opción viable.

La solución es el índice de prefijo. Almacena los primeros doce caracteres de la clave en una columna de texto plano prefix con un índice único. Cuando llega una solicitud, lee el prefijo, encuentra la única fila coincidente, luego aplica el hash a la clave completa presentada y compárala con el key_hash de esa fila. Una búsqueda indexada, un hash y una comparación en tiempo constante.

const prefix = key.slice(0, 12);
const record = await db.apiKey.findByPrefix(prefix);
if (!record || record.revokedAt) return res.status(401).end();

const presented = hashKey(key);
if (!timingSafeEqual(presented, record.keyHash)) {
  return res.status(401).end();
}

Algunos detalles son fundamentales para que esto funcione correctamente. El prefijo debe ser lo suficientemente largo para que las colisiones sean raras, pero lo suficientemente corto para que sea una etiqueta útil; doce caracteres es una elección común. Si ocurre una colisión, el índice único fallará durante la creación y deberás regenerarlo. Devuelve el mismo error tanto para un prefijo desconocido como para un secreto incorrecto, para que la respuesta no revele si un prefijo existe. Además, actualiza last_used_at de forma asíncrona o mediante una escritura por lotes, ya que una actualización síncrona en cada solicitud convierte una lectura en una escritura y duplica la carga de tu base de datos.

Asignación de permisos y límites a las claves

Una clave que puede hacer todo es una clave cuya filtración sería una catástrofe. Asigna a cada clave el conjunto más pequeño de permisos que le permita realizar su trabajo.

Los scopes son simplemente cadenas de permisos, los mismos átomos utilizados por RBAC: projects:read, deploy:write, billing:manage. Almacénalos en la clave, adjúntalos a la solicitud después de la verificación y aplícalos exactamente como lo harías con los permisos de un usuario.

router.post(
  "/deployments",
  apiKeyAuth(),
  requireScope("deploy:write"),
  createDeployment
);

Aquí es donde el principio de menor privilegio se vuelve concreto. Una integración de monitoreo solo necesita metrics:read. Un pipeline de CI necesita deploy:write pero nunca billing:manage. Un socio de solo lectura recibe scopes de lectura y nada más. Cuando una clave se filtra, el radio de impacto es únicamente aquello para lo que fue configurada, razón por la cual el valor predeterminado debe ser una lista corta que un humano extienda deliberadamente.

Los scopes también hacen que los rate limits sean naturales y justos. Debido a que cada clave es un emisor distinto, puedes asignar una cuota por clave y aplicarla en tu capa de limitación de tasa, evitando que un script descontrolado consuma la capacidad destinada a todos. Los planes por niveles suelen mapearse directamente a la cuota: las claves gratuitas tienen un límite bajo y las claves de pago uno más alto.

Prefijos de entorno

Los prefijos no son decorativos. Codifican el entorno y el tipo de una clave, lo que evita uno de los modos de fallo más comunes y vergonzosos: una clave de prueba apuntando a producción, o una clave de producción utilizada en una suite de pruebas que termina mutando datos reales.

Una convención habitual es sk_live_ para claves secretas en producción y sk_test_ para el sandbox. Las claves publicables o públicas podrían usar pk_live_. La nomenclatura depende de ti, pero mantenla consistente y documéntala, ya que tus usuarios confiarán en ella para saber de un vistazo a qué tiene acceso una clave.

sk_live_...  secret key, production, full access within its scopes
sk_test_...  secret key, sandbox, no real data
pk_live_...  publishable key, safe to embed in a browser

El prefijo también permite que tu servidor redirija las solicitudes al entorno correcto antes de realizar cualquier búsqueda. Una clave sk_test_ presentada a la API de producción puede ser rechazada inmediatamente con un mensaje claro, en lugar de fallar como un error de autenticación opaco. Y debido a que el prefijo es fijo y distintivo, los escáneres de secretos y las herramientas de revisión de código pueden configurarse para detectarlo.

Envío de una clave: header o query

El lugar por donde viaja la clave es tan importante como la forma en que se almacena. Envíala en un header.

GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...

Los headers no se incluyen en los logs de acceso predeterminados, no aparecen en el historial del navegador y no se reenvían en el header Referer cuando una página enlaza a otro sitio. También son fáciles de redactar en logs y proxies. El header Authorization con un esquema Bearer es lo convencional y es compatible con la mayoría de los clientes HTTP y herramientas; un header X-API-Key dedicado es igualmente válido.

Las query strings no son el lugar adecuado. Son capturadas por los logs de acceso, almacenadas en caché por intermediarios, guardadas en el historial del navegador y en analíticas, y se filtran a través de Referer. Una clave en una URL es una clave en una docena de lugares que no controlas.

GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1

Si un cliente realmente no puede configurar headers —como en algunos escenarios de webhooks heredados o embeds— utiliza en su lugar un token de corta duración y alcance limitado en la query string, y haz que expire rápidamente. Nunca aceptes una clave de larga duración en una URL y, si tus logs pudieran contener alguna, límpiala.

Rotación y revocación

Toda clave eventualmente necesitará cambiar. Un empleado se marcha, se pierde una laptop, una clave aparece en un repositorio público o una política de rutina simplemente requiere una rotación periódica. Diseña esto desde el principio, porque implementar la rotación a posteriori es doloroso.

La rotación emite un reemplazo sin afectar al cliente. El patrón es: crear una nueva clave con los mismos scopes, devolverla, permitir que ambas claves funcionen durante una breve ventana de superposición y luego revocar la antigua. La superposición es lo que hace que la rotación sea zero-downtime, y publicar su duración permite que los integradores puedan planificar.

// 1. Issue the replacement and return it to the owner.
// 2. Keep both keys valid for the overlap window (e.g. 24 hours).
// 3. Revoke the old key, or let it expire automatically.
await db.apiKey.revoke(oldKeyId);

La revocación es inmediata y permanente. Establece revoked_at en la fila y haz que el middleware de verificación rechace cualquier clave con un valor no nulo. No elimines la fila: mantenerla preserva la traza de auditoría y evita que se reutilice el mismo prefijo. La revocación debe surtir efecto en la siguiente solicitud, lo cual es sencillo cuando la clave se verifica contra la base de datos e imposible cuando se trata de un token autónomo.

Soporta la revocación de una sola clave, de todas las claves de un usuario y de todas las claves de un tenant. Las dos últimas son el botón de “creo que hemos sido vulnerados” y deberían ejecutarse con un solo clic. Alerta al propietario cada vez que se cree o revoque una clave, para que un atacante que obtenga acceso al dashboard no pueda generar una nueva credencial discretamente.

Filtraciones: git, logs y código de cliente

La mayoría de las vulneraciones de API keys no son ataques sofisticados. Simplemente es una clave que se encuentra en un lugar donde no debería estar.

El control de versiones es el caso clásico. Una clave pegada en un archivo de configuración, un fixture de prueba o un .env que se sube al repositorio queda en el historial de git para siempre, incluso si un commit posterior la elimina. Escanea los commits y pull requests en busca de patrones de claves, guarda los secretos en un gestor o en variables de CI, y rotalos inmediatamente si alguno llega a commitearse. Asume que cualquier clave en un repositorio público ya está comprometida.

Los logs son la filtración silenciosa. Un logger de peticiones que imprime URLs completas, headers o cuerpos puede capturar millones de claves. Configura tu logger para redactar los headers Authorization y X-API-Key, nunca registres los cuerpos de las peticiones en endpoints de autenticación y audita la salida de los logs en busca de cadenas con formato de clave.

El código del lado del cliente es el error fatal. Una clave en un bundle del navegador, una app móvil o un binario de escritorio puede extraerse en cuestión de minutos. No existe ninguna ofuscación que solucione esto. Coloca un servidor delante o emite tokens de corta duración.

Otros lugares a revisar: mensajes de error y stack traces, analíticas de terceros, capturas de pantalla en reportes de bugs y laptops de desarrolladores que se sincronizan con un backup compartido. Trata cada uno de estos puntos como una posible filtración y ofrece a los usuarios las herramientas necesarias para responder cuando esto suceda.

Monitoreo de uso y anomalías

Una clave que nunca se monitorea no puede defenderse. Registra suficiente información sobre cada uso para detectar el mal uso y para responder preguntas después de un incidente.

Como mínimo, almacena last_used_at y el origen de la solicitud. A partir de ahí, puedes crear alertas que detecten los patrones relevantes: una clave utilizada por primera vez en meses, un salto repentino en la tasa de solicitudes, un país de origen que no coincide con la integración o una ráfaga de errores 401 que sugiera que alguien está intentando adivinar prefijos.

logger.info({
  event: "api_key.used",
  keyId: record.id,
  ownerId: record.ownerId,
  route: req.path,
  ip: req.ip,
});

Nunca registres la clave en sí, solo su id y prefijo. Muestra el uso en el dashboard para que los clientes puedan ver qué claves están activas y detectar alguna que no reconozcan. Envía un correo electrónico al crear, rotar y revocar claves, y ofrece a los propietarios una forma de desactivar instantáneamente una clave sospechosa.

Para un tratamiento más amplio sobre cómo proteger una API contra el abuso, la guía de rate limiting cubre cuotas, manejo de ráfagas y cómo se integran los límites por clave.

Políticas de expiración y ciclo de vida

Una clave sin fecha de expiración es una clave que olvidarás hasta que se filtre. Asigna un ciclo de vida a cada clave, incluso si el valor predeterminado es generoso.

Una expiración opcional permite que el usuario cree una clave que caduque en una fecha elegida, lo cual es ideal para un contratista, una integración temporal o una migración puntual. Una edad máxima absoluta limita cuánto tiempo puede vivir cualquier clave, tras lo cual la rotación es obligatoria. Muchas plataformas combinan ambas: las claves tienen un predeterminado de un año, pueden ser más cortas y nunca pueden exceder los dos años.

Rastrea un estado en lugar de un simple booleano. Una clave puede estar activa, próxima a expirar, expirada o revocada, y cada estado merece una respuesta diferente. Las claves que estén cerca de expirar deberían disparar un correo electrónico para que el propietario realice la rotación antes de que ocurra una interrupción del servicio, y el middleware de verificación debería tratar las claves expiradas y revocadas de la misma manera: rechazarlas con un 401.

function isUsable(key: ApiKey): boolean {
  if (key.revokedAt) return false;
  if (key.expiresAt && key.expiresAt < new Date()) return false;
  return true;
}

La expiración es una red de seguridad, no un sustituto de la revocación. Una clave robada que expira en un año sigue siendo peligrosa, por lo que la rotación y el monitoreo siguen siendo los controles principales. Sin embargo, un límite de expiración significa que una clave que alguien olvidó, o una abandonada por un empleado que dejó la empresa, eventualmente dejará de funcionar por sí sola.

Probando la autenticación mediante API key

La verificación de la API key es una pequeña cantidad de código que protege una gran cantidad de acceso, por lo que debes probarla exhaustivamente y, principalmente, con casos negativos.

import request from "supertest";
import app from "../app.js";

test("rejects a request with no key", async () => {
  await request(app).get("/v1/projects").expect(401);
});

test("rejects a malformed key", async () => {
  await request(app)
    .get("/v1/projects")
    .set("Authorization", "Bearer not-a-real-key")
    .expect(401);
});

test("rejects a revoked key", async () => {
  const { key } = await createKey();
  await revokeKey(key);
  await request(app)
    .get("/v1/projects")
    .set("Authorization", `Bearer ${key}`)
    .expect(401);
});

test("rejects a key without the required scope", async () => {
  const { key } = await createKey({ scopes: ["projects:read"] });
  await request(app)
    .post("/v1/deployments")
    .set("Authorization", `Bearer ${key}`)
    .expect(403);
});

Añade pruebas que demuestren que la clave completa no se almacena: crea una clave, inspecciona la fila y asegura que key_hash sea el hash y que el texto plano no aparezca en ningún lugar. Asegura que last_used_at avance al usarse. Y prueba explícitamente el solapamiento de la rotación: tanto la clave antigua como la nueva deberían funcionar durante la ventana de tiempo, y solo la nueva una vez que esta se cierre.

Finalmente, prueba que las respuestas de error no distingan entre un prefijo desconocido y un secreto incorrecto. Ambos deben devolver el mismo estado y cuerpo; de lo contrario, le habrás dado a un atacante una forma de confirmar qué prefijos existen.

Construyendo la UI de gestión de claves

La pantalla de gestión es donde las propiedades de seguridad se vuelven visibles para los usuarios, por lo que debe lograr que el comportamiento correcto sea el comportamiento más sencillo.

La vista de lista muestra el nombre, prefijo, scopes, fecha de creación y último uso de cada clave, pero nunca el secreto. Ofrece un botón de revocación con una confirmación, y establece la “creación de clave” como la vía para la rotación. El flujo de creación permite al usuario elegir los scopes y una expiración opcional, para luego mostrar la clave completa una sola vez con un botón de copiado y una advertencia inequívoca.

sk_live_8f2a...   CI deploy      deploy:write       created 3 days ago
sk_live_1c4b...   Monitoring     metrics:read       last used 2 minutes ago
sk_test_9a7d...   Staging        projects:read      revoked yesterday

Detalles útiles a incluir: una marca de tiempo de “último uso” para que los usuarios puedan detectar claves que no reconozcan, una revocación en un solo clic para una clave individual, un “revocar todo” para la cuenta y notificaciones por correo electrónico en cada creación y revocación. Si muestras un gráfico de uso, hazlo por clave, ya que esa es la unidad de análisis para el usuario.

No construyas un botón de “revelar clave”. Este no puede existir si aplicas el hash correctamente, y su ausencia es una funcionalidad: significa que una filtración de la base de datos es sobrevivible. Explica en la UI que las claves se muestran una sola vez y que la rotación es la vía de recuperación, para que la restricción se sienta deliberada y no como un error.

Elegir una codificación y longitud

La codificación es una decisión menor con algunas consecuencias prácticas. Base64url es la opción más común porque es compacta, segura para URLs y headers, y distingue entre mayúsculas y minúsculas, lo que maximiza la entropía por carácter. Hex es más larga, pero más fácil de leer en voz alta y de rastrear en los logs. Base62 se encuentra en un punto medio y evita completamente + y /.

Codificación 128 bits 256 bits Notas
base64url 22 caracteres 43 caracteres Compacta, segura para URL, distingue mayúsculas/minúsculas
hex 32 caracteres 64 caracteres Más larga, fácil de copiar, no distingue mayúsculas/minúsculas
base62 22 caracteres 43 caracteres Solo alfanumérica, sin símbolos

Sea cual sea tu elección, mantén el secreto case-sensitive y nunca lo conviertas a minúsculas antes de compararlo. Un error común es tener un middleware o proxy que normaliza los valores de los headers, rompiendo silenciosamente las claves que contienen letras mayúsculas. Documenta el formato exacto y haz que el prefijo sea lo suficientemente distintivo para que una clave sea reconocible en un ticket de soporte sin que sea útil para un atacante.

No hagas que la clave sea autodescriptiva más allá del prefijo. Incrustar el ID de usuario, un checksum o una fecha de expiración dentro de la clave puede tentarte a omitir la consulta en la base de datos, pero también significa que la clave transporta información y, de todos modos, no podría revocarse sin realizar dicha consulta. Mantén el secreto opaco y deja que la base de datos gestione el significado.

Almacenamiento seguro de secretos del cliente

El almacenamiento en el lado del servidor es solo la mitad de la historia; el cliente también debe mantener la clave a salvo. Ofrece a los integradores una guía clara y directa, ya que el comportamiento habitual de muchos desarrolladores es pegar una clave en un archivo y hacer commit de la misma.

Recomienda el uso de variables de entorno para servidores, inyectadas por la plataforma o por un gestor de secretos en lugar de escribirlas en un .env versionado. En CI, utiliza el almacén de secretos del pipeline y enmascara el valor en los logs. Para el desarrollo local, carga los datos desde un .env que esté en el .gitignore, y prioriza el uso de claves sk_test_ para que cualquier error afecte únicamente a los datos del sandbox.

# Load from the environment, never hard-code.
export MYAPP_API_KEY="sk_live_8f2a9c..."
curl -H "Authorization: Bearer $MYAPP_API_KEY" https://api.example.com/v1/projects

Dirige a los usuarios hacia almacenes de secretos gestionados — AWS Secrets Manager, Google Secret Manager, Vault, o las variables integradas de la plataforma — y explica la rotación de claves en términos accionables. Si tu SDK lee la clave de una variable de entorno por defecto, la mayoría de las integraciones harán lo correcto sin necesidad de instrucciones, lo cual es el mejor tipo de control de seguridad.

Claves secretas y claves publicables

Muchas plataformas proporcionan dos tipos de claves, y confundirlas puede causar problemas graves. Una clave secreta autentica un servidor de confianza y nunca debe quedar expuesta. Una clave publicable está diseñada para integrarse en el código del cliente y es seguro que sea pública porque no otorga privilegios por sí misma.

sk_live_...  secret, server-only, carries scopes and a quota
pk_live_...  publishable, client-safe, identifies the account only

Una clave publicable es útil para la atribución y la limitación de tasa (rate limiting) en el navegador, pero cada operación privilegiada debe seguir siendo autorizada por algo que el cliente no pueda falsificar: un token de corta duración emitido por tu servidor o una sesión de usuario. Trata la clave publicable como un identificador, no como una credencial, y nunca permitas que su sola presencia otorgue acceso.

Nombrar ambas de manera consistente hace que la distinción sea obvia a simple vista y permite que los escáneres de secretos, la revisión de código y la documentación refuercen la misma regla. Si un desarrollador llega a pegar una clave sk_ en el código del front-end, el prefijo por sí solo debería hacer que el error sea visible antes de que se despliegue.

Mejores prácticas

  • Genera el secreto utilizando un CSPRNG con al menos 128 bits de entropía, añadiendo un prefijo para facilitar la lectura y diferenciar el entorno.
  • Almacena únicamente un hash SHA-256 y un prefijo corto; nunca persistas la clave completa.
  • Muestra la clave completa una sola vez, justo en el momento de la creación, y establece la rotación como la vía de recuperación.
  • Compara los hashes en tiempo constante y devuelve el mismo error tanto para claves desconocidas como para claves inválidas.
  • Indexa el prefijo para que la verificación requiera una sola búsqueda y un solo hash.
  • Asigna a cada clave los permisos mínimos necesarios y adjunta un límite de tasa (rate limit) por clave.
  • Codifica el entorno en el prefijo y rechaza cualquier clave de sandbox en la API de producción.
  • Acepta las claves en el encabezado Authorization, nunca en una query string.
  • Soporta la rotación sin tiempo de inactividad (zero-downtime) mediante una ventana de solapamiento, y haz que la revocación sea inmediata.
  • Monitoriza last_used_at, genera alertas ante anomalías y notifica a los propietarios cada vez que haya un cambio en las credenciales.
  • Separa las claves secretas de las claves publicables, y nunca permitas que una clave publicable otorgue acceso por sí sola.
  • Documenta el formato de las claves, los scopes y la política de rotación para que los integradores puedan implementarlo sin tener que adivinar.

Errores comunes

  • Almacenar claves en texto plano asumiendo que la base de datos nunca tendrá una filtración.
  • Aplicar hashing a cada fila para encontrar una coincidencia en lugar de indexar un prefijo.
  • Usar Math.random o un UUID como secreto, reduciendo así el espacio de búsqueda.
  • Incrustar una clave de larga duración en un bundle del navegador, una aplicación móvil o un binario de escritorio.
  • Incluir la clave en una query string, donde los logs y el historial pueden capturarla.
  • Otorgar acceso total a cada clave porque definir el alcance (scoping) parecía trabajo extra.
  • No rotar ni hacer expirar las claves, haciendo que una filtración sea útil indefinidamente.
  • Eliminar filas revocadas y perder así el rastro de auditoría.
  • Registrar headers o URLs completas en los logs, filtrando claves en las herramientas de observabilidad.
  • Devolver un error diferente para un prefijo desconocido que para un secreto incorrecto.

Próximos pasos

Las API keys son las credenciales más sencillas de tu caja de herramientas, y los mismos instintos —hashear en reposo, limitar el alcance y rotar bajo demanda— se aplican en todas partes. Si necesitas tokens verificables de corta duración que contengan claims, lee la guía de JWT. Cuando el cliente actúa en nombre de un usuario y requiere su consentimiento, OAuth 2.0 es el modelo adecuado. Las cadenas de permisos de una clave son los mismos átomos utilizados por RBAC, por lo que esa guía es el complemento natural para definir el alcance. Y dado que las claves son un contenedor natural para las cuotas, la sección de rate limiting muestra cómo evitar que una sola integración agote la capacidad para todos los demás.

En la practica

Crear, verificar, asignar scope, revocar

Las cuatro operaciones que todo sistema de API keys necesita, y nada más.

routes/keys.ts
import crypto from "node:crypto";

router.post("/keys", requireAuth(), async (req, res) => {
  const { name, scopes } = req.body;
  const secret = crypto.randomBytes(32).toString("base64url");
  const key = `sk_live_${secret}`;

  const [row] = await db.apiKey.insert({
    name,
    ownerId: req.user.id,
    prefix: key.slice(0, 12),
    keyHash: crypto.createHash("sha256").update(key).digest("hex"),
    scopes,
  });

  // The only time the full key is ever returned.
  res.status(201).json({
    id: row.id,
    name: row.name,
    prefix: row.prefix,
    key,
  });
});

Almacena un hash, no la clave

Un hash es suficiente para verificar una clave presentada y es inútil para quien robe la tabla. Es el mismo razonamiento que se aplica a las contraseñas.

Preferir
CREATE TABLE api_keys (
  id         bigserial PRIMARY KEY,
  prefix     text NOT NULL,
  key_hash   text NOT NULL,
  scopes     text[] NOT NULL DEFAULT '{}',
  revoked_at timestamptz
);

CREATE INDEX api_keys_prefix_idx ON api_keys (prefix);
Evitar
CREATE TABLE api_keys (
  id  bigserial PRIMARY KEY,
  key text NOT NULL
  -- one database dump or backup leak
  -- hands over every customer's key
);

Envía las claves en una cabecera

Las cabeceras no se registran por defecto, no aparecen en el historial del navegador y no se filtran a través de la cabecera Referer cuando una página enlaza a otro sitio.

Preferir
GET /v1/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_8f2a9c...
Evitar
GET /v1/projects?api_key=sk_live_8f2a9c... HTTP/1.1
Host: api.example.com
# query strings land in access logs, proxies
# and browser history

Compromisos

¿Son las API keys la credencial adecuada?

Las claves son simples y universales, lo cual es tanto su fortaleza como su debilidad. Entiende qué sacrificas.

Strengths

  • Extremadamente simples para los clientes

    No hay flujo de login, ni refresh token, ni desincronización de reloj. Un cliente almacena una cadena y la envía, por eso cada CLI y plataforma para desarrolladores usa claves.

  • Revocables por integración

    Cada clave es una credencial nombrada. Puedes revocar la clave utilizada por un script filtrado sin afectar a ningún otro cliente o servicio.

  • Fáciles de limitar y medir

    Una clave es una unidad natural para permisos y rate limits, permitiéndote dar a una integración acceso de solo lectura y una cuota modesta sin construir nada nuevo.

Trade-offs

  • De larga duración por naturaleza

    Las claves normalmente no expiran, por lo que una filtrada es peligrosa hasta que alguien lo note. Expiraciones cortas, rotación y monitoreo reducen esta ventana.

  • Sin contexto de usuario

    Una clave identifica un servicio, no a una persona. Cualquier cosa que requiera consentimiento, acceso delegado o auditoría por usuario pertenece a OAuth.

  • Difíciles de mantener secretas en el cliente

    Una clave embebida en una app móvil o un bundle de navegador es pública. Usa un proxy de backend o un token de corta duración para esos clientes.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender API Key Management?

Nuestro tutorial interactivo te guia a traves de API Key Management paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.