Ir al contenido

Autenticación y claves de API

La API de DUST autentica cada solicitud a /api/v1/* mediante un JWT de portador emitido por AuthD, el servicio de cuentas de DUST. Las integraciones con la API actúan como una cuenta de servicio —una identidad de máquina propiedad de tu organización—, nunca como una persona. El flujo es el siguiente:

  1. Un administrador de la organización crea una cuenta de servicio y le emite una credencial (una sola vez).
  2. Tu integración intercambia la credencial por un token de portador de corta duración.
  3. Envía Authorization: Bearer <token> en las llamadas a la API y vuelve a realizar el intercambio cuando el token caduque.

Una cuenta de servicio es una identidad de máquina de primera clase: pertenece exactamente a una organización, se le puede conceder acceso a equipos como a cualquier miembro y cada acción que realiza queda registrada en el libro de auditoría como efectuada por la cuenta de servicio, no por el empleado que la haya configurado. Sus credenciales pueden rotarse o revocarse en cualquier momento sin modificar la cuenta personal de nadie.

Hay dos tipos de credenciales disponibles, y una cuenta de servicio puede tener ambos:

  • Clave de API —la integración más sencilla: intercambia la clave por un token mediante una única llamada HTTP.
  • Cliente OAuth2 (client_credentials) —para middleware empresarial (SAP Integration Suite, MuleSoft, Boomi, …) con compatibilidad integrada con OAuth2.

Los administradores de la organización gestionan las cuentas de servicio y sus credenciales en el portal de AuthD, en authd.dustid.io.

  1. Inicia sesión en authd.dustid.io como administrador de la organización.
  2. Abre la página de tu organización y selecciona la pestaña Cuentas de servicio.
  3. Crea una cuenta de servicio (por ejemplo, «Conector de SAP» o «Estación de escaneo de la línea 3»).
  4. Abre Administrar en la cuenta de servicio y crea una clave de API.
  5. Guarda la clave en un gestor de secretos y trátala como una contraseña. Solo se muestra una vez.

Lee esto antes del primer ejemplo: el lugar donde reside una credencial es la decisión fundamental que determina la seguridad de una integración con DUST.

  • Las credenciales solo residen en tus servidores —en variables de entorno o en un gestor de secretos; nunca en paquetes de cliente ni en el control de código fuente.
  • Los tokens de portador también son credenciales. Tienen una duración breve, pero un token generado a partir de tu credencial actúa con el acceso completo de la cuenta de servicio: todas las organizaciones, los Equipos y las operaciones a los que pueda acceder esa cuenta. Una duración breve limita el tiempo de exposición, no el alcance de los daños.
  • Si tu aplicación web o móvil necesita datos de DUST, el patrón admitido es navegador → tu backend → API de DUST. Tu backend conserva la credencial, genera el token de portador, decide qué contexto y qué operación se permiten al solicitante y llama directamente a la API de DUST. El navegador nunca recibe credenciales de DUST de ningún tipo. Las integraciones móviles y de escáner siguen exactamente este esquema: la captura se envía a tu backend, que llama a los endpoints de Identificadores con credenciales almacenadas en el servidor.
  • Una cuenta de servicio por aplicación y entorno permite realizar rotaciones, revocaciones y auditorías de forma precisa.

Intercambiar la clave por un token de portador

Sección titulada «Intercambiar la clave por un token de portador»

GET /api/auth/token recibe la clave de API en la cabecera x-api-key y devuelve un JWT. (APID redirige esta solicitud a AuthD, por lo que una única URL base sirve para todo).

Esta llamada, y todas las llamadas basadas en su resultado, se ejecutan en un servidor.

Ventana de terminal
curl -fsS "https://apid.dustid.io/api/auth/token" \
-H "x-api-key: $DUST_API_KEY"

Respuesta:

{ "token": "eyJhbGciOi...", "expiresIn": 900, "expiresAt": "2026-07-14T22:40:00.000Z" }

expiresIn es el tiempo de vida restante del token en segundos; expiresAt representa el mismo momento como una marca de tiempo ISO 8601. Ambos se derivan de la propia declaración de caducidad del token, por lo que un token emitido sin ella se devuelve únicamente como { "token": "…" }; léelos de manera defensiva y, si no están presentes, aplica tu propio margen conservador. Utiliza cualquiera de los dos para programar el siguiente intercambio; no codifiques una duración fija.

Para plataformas compatibles de forma nativa con OAuth2, crea un cliente OAuth en la cuenta de servicio en lugar de una clave de API, o además de ella. El identificador y el secreto del cliente solo se muestran una vez al crearlos.

Solicita un token al endpoint de tokens de la cuenta de servicio mediante la concesión estándar client_credentials; se aceptan tanto client_secret_post (campos de formulario) como client_secret_basic (HTTP Basic):

Ventana de terminal
curl -fsS "https://authd.dustid.io/api/auth/dust/service-accounts/token" \
-d grant_type=client_credentials \
-d client_id="$DUST_CLIENT_ID" \
-d client_secret="$DUST_CLIENT_SECRET"

Respuesta (respuesta estándar de token OAuth2):

{ "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 900 }

El token resultante tiene exactamente el mismo formato y los mismos permisos que uno obtenido mediante el intercambio de una clave de API; utilízalo del mismo modo. Si tu middleware solicita una «URL de token», utiliza el endpoint anterior.

Envía el token en cada llamada a la API principal:

Authorization: Bearer <token>

Una forma rápida de confirmar que el token funciona:

Ventana de terminal
curl -fsS "https://apid.dustid.io/api/v1/me" \
-H "Authorization: Bearer $DUST_TOKEN"

Las solicitudes sin un token válido reciben un 401 con el cuerpo { "code": "UNAUTHORIZED", "message": "...", "status": 401 }; consulta Convenciones de las solicitudes para conocer el contrato de errores y Errores y resultados de escaneo para ver la lista completa de códigos.

Los tokens de portador de las cuentas de servicio tienen una duración breve —actualmente 15 minutos—, pero debes leer siempre la duración de la respuesta (expiresIn/expiresAt en el intercambio de claves, expires_in en la concesión OAuth) en lugar de codificarla de forma fija. No hay token de renovación: cuando un token caduca, vuelve a intercambiar la credencial.

Un cliente robusto combina ambos patrones: renueva de forma proactiva con un margen de seguridad y trata un primer 401 como señal para renovar y volver a intentar la solicitud (esto también cubre los desfases del reloj y las revocaciones durante el periodo de validez):

let cached: { token: string; refreshAfter: number } | null = null;
async function getToken(): Promise<string> {
if (cached && Date.now() < cached.refreshAfter) return cached.token;
const res = await fetch("https://apid.dustid.io/api/auth/token", {
headers: { "x-api-key": process.env.DUST_API_KEY! },
});
if (!res.ok) throw new Error(`token exchange failed: ${res.status}`);
const { token, expiresIn } = await res.json();
// refresh 60s before expiry, never cache a token for less than 5s
cached = { token, refreshAfter: Date.now() + Math.max(expiresIn - 60, 5) * 1000 };
return token;
}
async function apiFetch(url: string, init: RequestInit = {}): Promise<Response> {
const call = async () => {
// new Headers() handles every HeadersInit shape (plain object, Headers,
// tuple array) — an object spread would silently drop the latter two.
const headers = new Headers(init.headers);
headers.set("Authorization", `Bearer ${await getToken()}`);
return fetch(url, { ...init, headers });
};
let res = await call();
if (res.status === 401) {
cached = null; // token revoked or expired early — refresh once and retry
res = await call();
}
return res;
}

El intercambio tiene un coste bajo; no crees cachés prolongadas a su alrededor. La corta duración también forma parte de tu estrategia ante incidentes: revocar una credencial impide de inmediato la emisión de nuevos tokens y cualquier token ya emitido caduca en cuestión de minutos.

Una cuenta de servicio autentica el sistema, pero no puede indicar a DUST qué persona pulsó el botón en tu ERP o en tu planta de producción. Si deseas disponer de esa trazabilidad, declárala en cada solicitud mediante la cabecera Dust-Ctx-Declared-Actor, que contiene un pequeño objeto JSON:

Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP", "displayName": "Jane Doe"}
  • id es obligatorio; system, displayName y role son opcionales. El valor puede codificarse como URI (es obligatorio si contiene caracteres que no sean ASCII) y debe ocupar menos de 1 KB.
  • El actor declarado se registra literalmente en cada evento que escribe la solicitud y aparece en el historial de actividad como atribución declarada: la proporciona tu integración, DUST no la verifica y nunca concede ni restringe permisos.
  • Un administrador de la organización puede establecer como obligatoria la política de atribución de una cuenta de servicio; en ese caso, las solicitudes de escritura sin un actor declarado se rechazan con 403 ATTRIBUTION_REQUIRED.

La API verifica la firma de cada token de portador mediante el conjunto de claves web JSON de AuthD y comprueba las declaraciones del emisor (https://authd.dustid.io/api/auth en producción) y del destinatario. Normalmente nunca necesitarás conocer este detalle, pero si tu propio backend quiere verificar los JWT emitidos por DUST (por ejemplo, para confiar en un token reenviado desde otro servicio interno), el JWKS es público:

GET https://apid.dustid.io/api/auth/jwks

Devuelve un documento estándar { "keys": [ ... ] } que puede utilizarse con cualquier biblioteca JOSE.

Una integración de escaneo en navegador, de principio a fin

Sección titulada «Una integración de escaneo en navegador, de principio a fin»

El siguiente patrón muestra cómo debe ser cualquier integración de escaneo para navegador o móvil. Dos archivos, dos ubicaciones de ejecución y una credencial, que nunca sale del segundo archivo.

scanner.tsx — runs in the BROWSER
// No DUST credential appears in this file, and none should.
async function identify(capture: Blob) {
const form = new FormData();
form.set("capture", capture);
// Your own endpoint, authenticated with your own session.
const response = await fetch("/api/identify", { method: "POST", body: form, credentials: "same-origin" });
return await response.json();
}
server/identify.ts — runs on YOUR SERVER
// Holds the DUST credential, mints the bearer token, chooses the context,
// and applies your own authorization before calling DUST.
export async function handleIdentify(request: Request, session: YourSession) {
if (!session.mayScan) return new Response("Forbidden", { status: 403 });
const capture = (await request.formData()).get("capture") as Blob;
const form = new FormData();
form.set("tagType", "DUST");
form.set("data", capture);
form.set("searchTeamIds", JSON.stringify(session.allowedTeamIds));
return await fetch("https://apid.dustid.io/api/v1/tags/identify", {
method: "POST",
headers: {
Authorization: `Bearer ${await getToken()}`, // server-held credential
"Dust-Ctx-Org-Id": session.organizationId, // your choice, not the caller's
},
body: form,
});
}

Tu proxy también es el lugar natural para aplicar reglas por usuario que la API de DUST no puede conocer: en qué Equipos puede buscar este empleado, si puede vincular además de identificar y qué información registras.

Puede haber varias credenciales activas simultáneamente en una misma cuenta de servicio, por lo que una rotación nunca requiere tiempo de inactividad:

  1. Crea una clave de sustitución (o un cliente OAuth) en la misma cuenta de servicio.
  2. Despliégala en tu aplicación (ambas credenciales funcionan durante el periodo de solapamiento).
  3. Confirma que el tráfico de producción utiliza la nueva credencial; en el portal se muestra cuándo se usó por última vez cada clave.
  4. Revoca la credencial antigua.