Claves de API
Una clave de API es una credencial de larga duración para máquinas — trabajos
de CI, scripts, llamadas de servidor a servidor — que no pueden presentar un
formulario de inicio de sesión ni llevar una cookie de sesión. El cliente envía
la clave en cada petición; el servidor la busca e identifica al llamante.
Rustango te ofrece dos capas: un ayudante autónomo de generación/verificación
que puedes conectar a tu propia tabla, y un backend listo para usar que almacena
las claves y autentica las peticiones Authorization: Bearer.
¿Algún término te resulta nuevo? Token, hash, Bearer, argon2id — el glosario define los bloques de construcción.
Fuente:
rustango::api_keys(generate_key,hash_secret,verify_key,split_token,ApiKeyError) — el ayudante autónomo, tras la característicaapi_keys(activada por defecto). El backend con almacenamiento esrustango::tenancy::auth_backends(create_api_key,ApiKeyBackend,ensure_api_keys_table_pool) — tras la característicatenancy.Versión ejecutable: los fragmentos del ayudante están copiados de
auth_api_keys_doc.rs(cargo test -p rustango --test auth_api_keys_doc); el flujo del middlewareApiKeyBackenddeauth_backends_doc.rs(cargo test -p rustango --features sqlite,tenancy --test auth_backends_doc).
Tabla de contenidos
- Cómo funciona una clave de API
- El ayudante autónomo
- El backend con almacenamiento
- Emitir una clave (CLI + código)
- Autenticar peticiones
- Notas de seguridad
- Véase también
Cómo funciona una clave de API
Una clave tiene dos partes unidas por un punto: {prefix}.{secret}.
- El prefijo tiene 8 caracteres — se almacena en texto claro y se usa como índice de búsqueda rápido y único («¿qué clave es esta?»).
- El secreto es la credencial real. Solo almacenas un hash argon2id del mismo, nunca el secreto en sí.
El token completo se muestra al usuario exactamente una vez, en la creación. Piérdelo y lo reemites — no hay forma de recuperarlo, porque solo se conserva el hash. Es la misma disciplina de «hashea, no almacenes» que en las contraseñas, aplicada a las credenciales de máquina.
El ayudante autónomo
rustango::api_keys es un conjunto de herramientas sin dependencias (sin base de
datos, sin tablas) — úsalo cuando quieras almacenar las claves en tu propio
esquema.
use rustango::api_keys::{generate_key, split_token, verify_key};
// En la creación: devuelve (full_token, prefix, hash).
let (token, prefix, hash) = generate_key()?;
// → token = "a1b2c3d4.<secret>" mostrar al usuario UNA VEZ
// → prefix = "a1b2c3d4" almacenar como clave de búsqueda
// → hash = "$argon2id$v=19$..." almacenar en lugar del secreto
// En una petición entrante: extraer el token, encontrar la fila por prefijo, verificar.
let (prefix, secret) = split_token(&token).expect("well-formed token");
let stored_hash = lookup_hash_by_prefix(prefix); // tu consulta
if verify_key(secret, &stored_hash)? {
// autenticado
}
split_token es estricto — devuelve None a menos que el prefijo tenga
exactamente 8 caracteres y el secreto no esté vacío, de modo que una entrada
malformada se rechaza antes de que toques la base de datos:
assert!(split_token("no-dot-here").is_none());
assert!(split_token("short.secret").is_none()); // el prefijo debe tener 8 caracteres
assert!(split_token("a1b2c3d4.").is_none()); // secreto vacío
hash_secret y verify_key usan argon2id con una sal aleatoria por hash, de
modo que hashear el mismo secreto dos veces produce cadenas distintas — y ambas
se verifican. verify_key devuelve Ok(false) en caso de discrepancia y
Err(ApiKeyError) solo cuando la cadena almacenada no es un hash válido.
El backend con almacenamiento
Si ya estás en la capa tenancy, no necesitas tu propia tabla.
rustango::tenancy::auth_backends incluye un modelo ApiKey (tabla
rustango_api_keys), un creador, y un backend de autenticación que se conecta a
la cadena de backends.
Inicializa la tabla una vez (tri-dialecto, idempotente):
use rustango::tenancy::auth_backends::ensure_api_keys_table_pool;
ensure_api_keys_table_pool(&pool).await?; // CREATE TABLE IF NOT EXISTS
La fila ApiKey almacena user_id (FK a rustango_users), el key_prefix de
8 caracteres (único), el key_hash argon2id, una label, y un expires_at
opcional.
Emitir una clave
create_api_key genera el token, hashea el secreto, inserta la fila, y devuelve
el token en texto claro una sola vez:
use rustango::tenancy::auth_backends::create_api_key;
// Emitir una clave sin expiración para el usuario 42, etiquetada "ci-key".
let token = create_api_key(42, "ci-key", None, &pool).await?;
println!("Store this — it won't be shown again: {token}");
// O con una expiración:
use chrono::{Duration, Utc};
let token = create_api_key(42, "tmp", Some(Utc::now() + Duration::days(30)), &pool).await?;
Desde la línea de comandos, la CLI manage envuelve la misma llamada:
cargo run -- create-api-key <tenant> <username> --label "ci-key" --expires-days 30
Autenticar peticiones
Registra ApiKeyBackend en tu cadena de backends de
autenticación y el middleware autentica cualquier petición
Authorization: Bearer {prefix}.{secret}:
use std::sync::Arc;
use rustango::tenancy::auth_backends::{ApiKeyBackend, AuthBackend, ModelBackend};
use rustango::tenancy::RouterAuthExt;
let backends: Vec<Arc<dyn AuthBackend>> = vec![
Arc::new(ModelBackend), // HTTP Basic (humanos)
Arc::new(ApiKeyBackend), // clave Bearer (máquinas)
];
let app = Router::new()
.route("/api/data", get(handler))
.require_auth(backends, pool);
Un cliente entonces llama:
curl https://api.example.com/api/data \
-H "Authorization: Bearer a1b2c3d4.the-secret-half"
El backend encuentra la ApiKey por su prefijo de 8 caracteres, comprueba
expires_at, verifica el secreto contra el hash almacenado, carga al usuario
propietario, y lo inyecta para que tus handlers lo lean mediante
CurrentUser. Un secreto incorrecto o un prefijo
desconocido es un 401; una clave caducada se rechaza; un propietario
deshabilitado es un 403.
Notas de seguridad
- El secreto se muestra una vez. Solo se persisten el prefijo + el hash argon2id — no hay recuperación, solo reemisión.
- El prefijo se almacena en texto claro a propósito — es el índice de búsqueda O(1). Una fuga de base de datos revela qué prefijos existen, nunca los secretos.
- El tiempo está igualado. Un prefijo desconocido igualmente ejecuta una verificación ficticia, de modo que una clave ausente tarda más o menos lo mismo que una real — sin enumeración a través del tiempo de respuesta.
- Limita las claves a un usuario, establece una expiración, y rótalas. Emite
una por integración para poder revocar una sin perturbar las demás; prefiere
ventanas
expires_atcortas para acceso temporal. - Desambiguación de los JWT: el backend trata un valor Bearer como una clave
de API solo cuando su primer segmento separado por puntos tiene exactamente 8
caracteres — así las claves de API y los JWT pueden compartir la
cabecera
Authorization: Bearer.
Véase también
- Backends de autenticación — la cadena a la que se conecta
ApiKeyBackend, y el extractorCurrentUser+ el middlewarerequire_auth/require_perm. - Firma de peticiones HMAC — para llamantes de máquina que necesitan integridad por petición, no solo una credencial bearer.
- Contraseñas — la misma disciplina de «hashea, no almacenes» para los inicios de sesión humanos.
- JWT — tokens sin estado de corta duración, la otra opción de máquina.
