Salta ai contenuti

Autenticazione e chiavi API

L’API DUST autentica ogni richiesta /api/v1/* con un JWT bearer emesso da AuthD, il servizio account di DUST. Le integrazioni API operano come un account di servizio — un’identità macchina di proprietà della tua organizzazione — mai come una persona. Il flusso è il seguente:

  1. Un amministratore dell’organizzazione crea un account di servizio e gli assegna una credenziale (una sola volta).
  2. La tua integrazione scambia la credenziale con un token bearer di breve durata.
  3. Invia Authorization: Bearer <token> nelle chiamate API ed esegui nuovamente lo scambio alla scadenza del token.

Un account di servizio è un’identità macchina di prima classe: appartiene esattamente a un’organizzazione, può ricevere l’accesso ai team come un membro e ogni azione che esegue viene registrata nel registro di audit come compiuta dall’account di servizio, non dal dipendente che lo ha configurato. Le sue credenziali possono essere ruotate o revocate in qualsiasi momento senza intervenire sull’account personale di nessuno.

Sono disponibili due tipi di credenziali e un singolo account di servizio può averli entrambi:

  • Chiave API — l’integrazione più semplice: scambia la chiave con un token mediante una sola chiamata HTTP.
  • Client OAuth2 (client_credentials) — per middleware aziendali (SAP Integration Suite, MuleSoft, Boomi, …) con supporto OAuth2 integrato.

Gli account di servizio e le relative credenziali sono gestiti dagli amministratori dell’organizzazione nel portale AuthD all’indirizzo authd.dustid.io.

  1. Accedi a authd.dustid.io come amministratore dell’organizzazione.
  2. Apri la pagina della tua organizzazione e seleziona la scheda Account di servizio.
  3. Crea un account di servizio (ad esempio, “Connettore SAP” o “Postazione scanner della linea 3”).
  4. Apri Gestisci nell’account di servizio e crea una chiave API.
  5. Conserva la chiave in un gestore di segreti: trattala come una password. Viene mostrata una sola volta.

Leggi questa sezione prima del primo esempio: il luogo in cui risiede una credenziale è la singola decisione che determina la sicurezza o l’insicurezza di un’integrazione DUST.

  • Le credenziali risiedono esclusivamente sui tuoi server — in variabili di ambiente o in un gestore di segreti, mai nei bundle client e mai nel controllo del codice sorgente.
  • Anche i token bearer sono credenziali. Hanno una durata breve, ma un token generato dalla tua credenziale opera con l’accesso completo dell’account di servizio: ogni organizzazione, Team e operazione accessibile da tale account. Una durata breve limita l’intervallo temporale, non la portata del danno.
  • Se la tua app web o mobile necessita di dati DUST, il modello supportato è browser → il tuo backend → API DUST. Il tuo backend conserva la credenziale, genera il token bearer, decide quale contesto e quale operazione sono consentiti al chiamante ed effettua direttamente la chiamata all’API DUST. Il browser non riceve mai alcun tipo di credenziale DUST. Le integrazioni con scanner e dispositivi mobili seguono esattamente questo modello: l’acquisizione viene inviata al tuo backend, che chiama gli endpoint degli Identificatori usando credenziali conservate sul server.
  • Un account di servizio per ogni applicazione e ambiente consente rotazioni, revoche e audit mirati.

GET /api/auth/token riceve la chiave API nell’header x-api-key e restituisce un JWT. (APID inoltra la richiesta ad AuthD, quindi un solo URL di base copre tutto.)

Questa chiamata, e ogni chiamata basata sul suo risultato, viene eseguita su un server.

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

Risposta:

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

expiresIn indica la durata residua del token in secondi; expiresAt indica lo stesso momento come timestamp ISO 8601. Entrambi derivano dall’attestazione di scadenza del token stesso, quindi un token emesso senza tale attestazione viene restituito soltanto come { "token": "…" }: leggili in modo difensivo e, se assenti, applica un tuo margine prudenziale. Usa uno dei due per pianificare lo scambio successivo; non codificare una durata fissa.

Per le piattaforme che supportano OAuth2 in modo nativo, crea un client OAuth nell’account di servizio al posto di una chiave API, oppure in aggiunta a essa. L’ID client e il segreto vengono mostrati una sola volta al momento della creazione.

Richiedi un token all’endpoint dei token dell’account di servizio usando il grant standard client_credentials: sono accettati sia client_secret_post (campi del modulo) sia client_secret_basic (HTTP Basic):

Terminal window
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"

Risposta (risposta token OAuth2 standard):

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

Il token risultante è identico per struttura e diritti a quello ottenuto dallo scambio della chiave API: usalo nello stesso modo. Se il tuo middleware richiede un “URL del token”, usa l’endpoint riportato sopra.

Invia il token con ogni chiamata all’API principale:

Authorization: Bearer <token>

Un modo rapido per verificare che il token funzioni:

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

Le richieste prive di un token valido ricevono 401 con corpo { "code": "UNAUTHORIZED", "message": "...", "status": 401 }: consulta Convenzioni delle richieste per il contratto degli errori ed Errori ed esiti delle scansioni per l’elenco completo dei codici.

I token bearer degli account di servizio hanno una durata breve — attualmente 15 minuti — ma leggi sempre la durata dalla risposta (expiresIn/expiresAt per lo scambio della chiave, expires_in per il grant OAuth) anziché codificarla in modo fisso. Non esiste un refresh token: quando un token scade, scambia nuovamente la credenziale.

Un client robusto combina entrambi i modelli: rinnova in modo proattivo applicando un margine di sicurezza e considera una singola risposta 401 come segnale per rinnovare e riprovare (questo copre anche lo scostamento degli orologi e la revoca durante il periodo di validità):

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;
}

Lo scambio è poco costoso; non costruire intorno a esso cache di lunga durata. La breve durata è anche una tutela in caso di incidente: la revoca di una credenziale interrompe immediatamente l’emissione di nuovi token e qualsiasi token già emesso scade entro pochi minuti.

Un account di servizio autentica il sistema; non può comunicare a DUST quale persona abbia premuto il pulsante nel tuo ERP o nello stabilimento. Se desideri questa tracciabilità, dichiarala per ogni richiesta mediante l’header Dust-Ctx-Declared-Actor, un piccolo oggetto JSON:

Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP", "displayName": "Jane Doe"}
  • id è obbligatorio; system, displayName e role sono facoltativi. Il valore può essere codificato come URI (obbligatorio se contiene caratteri non ASCII) e deve rimanere inferiore a 1 KB.
  • L’autore dichiarato viene registrato testualmente in ogni evento scritto dalla richiesta e mostrato nella cronologia delle attività come attribuzione dichiarata: viene fornito dalla tua integrazione, non è verificato da DUST e non concede né limita mai le autorizzazioni.
  • Un amministratore dell’organizzazione può impostare su obbligatori i criteri di attribuzione di un account di servizio; in tal caso, le richieste di scrittura prive di un autore dichiarato vengono rifiutate con 403 ATTRIBUTION_REQUIRED.

L’API verifica la firma di ogni token bearer usando il JSON Web Key Set di AuthD e controlla le attestazioni dell’emittente (https://authd.dustid.io/api/auth in produzione) e del destinatario. Normalmente non hai mai bisogno di conoscere questo dettaglio, ma se il tuo backend desidera verificare i JWT emessi da DUST (ad esempio, per considerare attendibile un token inoltrato da un altro servizio interno), il JWKS è pubblico:

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

Restituisce un documento standard { "keys": [ ... ] }, utilizzabile con qualsiasi libreria JOSE.

Un’integrazione di scansione dal browser, dall’inizio alla fine

Sezione intitolata “Un’integrazione di scansione dal browser, dall’inizio alla fine”

Il modello seguente è quello che dovrebbe adottare ogni integrazione di scansione da browser o dispositivo mobile. Due file, due ambienti di esecuzione, una credenziale, che non lascia mai il secondo file.

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,
});
}

Il tuo proxy è anche il luogo naturale in cui applicare le regole per utente che l’API DUST non può conoscere: in quali Team può effettuare ricerche il dipendente, se può associare oltre che identificare e quali informazioni registrare.

Più credenziali possono essere attive contemporaneamente in un singolo account di servizio, quindi la rotazione non richiede mai tempi di inattività:

  1. Crea una chiave sostitutiva (o un client OAuth) nello stesso account di servizio.
  2. Distribuiscila nella tua applicazione (durante la sovrapposizione funzionano entrambe le credenziali).
  3. Verifica che il traffico di produzione utilizzi la nuova credenziale: nel portale è visibile l’ora dell’ultimo utilizzo di ciascuna chiave.
  4. Revoca la vecchia credenziale.