Pular para o conteúdo

Autenticação e chaves de API

A API DUST autentica todos os pedidos /api/v1/* com um JWT bearer emitido pelo AuthD, o serviço de contas da DUST. As integrações de API atuam como uma Conta de serviço — uma identidade de máquina pertencente à sua organização — e nunca como uma pessoa. O fluxo é o seguinte:

  1. Um administrador da organização cria uma Conta de serviço e emite-lhe uma credencial (uma única vez).
  2. A sua integração troca a credencial por um token bearer de curta duração.
  3. Envie Authorization: Bearer <token> nas chamadas à API e efetue uma nova troca quando o token expirar.

Uma Conta de serviço é uma identidade de máquina de pleno direito: pertence exatamente a uma organização, pode receber acesso a Equipas tal como um membro e todas as ações que realiza são registadas no livro de auditoria como sendo da Conta de serviço — não do funcionário que a configurou. As respetivas credenciais podem ser substituídas ou revogadas a qualquer momento sem afetar a conta pessoal de ninguém.

Estão disponíveis dois tipos de credenciais, e uma Conta de serviço pode ter ambos:

  • Chave de API — a integração mais simples: troque a chave por um token com uma única chamada HTTP.
  • Cliente OAuth2 (client_credentials) — para middleware empresarial (SAP Integration Suite, MuleSoft, Boomi, …) com suporte integrado para OAuth2.

As Contas de serviço e as respetivas credenciais são geridas pelos administradores da organização no portal AuthD em authd.dustid.io.

  1. Inicie sessão em authd.dustid.io como administrador da organização.
  2. Abra a página da sua organização e selecione o separador Contas de serviço.
  3. Crie uma Conta de serviço (por exemplo, “Conector SAP” ou “Estação de digitalização da linha 3”).
  4. Abra Gerir na Conta de serviço e crie uma chave de API.
  5. Guarde a chave num gestor de segredos — trate-a como uma palavra-passe. É apresentada uma única vez.

Leia esta secção antes do primeiro exemplo: o local onde uma credencial reside é a decisão determinante para a segurança de uma integração DUST.

  • As credenciais residem apenas nos seus servidores — em variáveis de ambiente ou num gestor de segredos, nunca em pacotes de cliente nem no controlo de código-fonte.
  • Os tokens bearer também são credenciais. São de curta duração, mas um token emitido a partir da sua credencial atua com o acesso total da Conta de serviço — todas as organizações, Equipas e operações a que essa conta consegue aceder. Uma duração curta limita o período de exposição, não o alcance do impacto.
  • Se a sua aplicação Web ou móvel precisar de dados da DUST, o padrão suportado é browser → o seu backend → API DUST. O seu backend conserva a credencial, emite o token bearer, decide que contexto e que operação são permitidos ao autor da chamada e chama diretamente a API DUST. O browser nunca recebe qualquer tipo de credencial DUST. As integrações de digitalização e móveis seguem exatamente este modelo: a captura é enviada para o seu backend, que chama os endpoints de Identificadores com credenciais mantidas no servidor.
  • Uma Conta de serviço por aplicação e ambiente permite efetuar substituições, revogações e auditorias de forma precisa.

GET /api/auth/token recebe a chave de API no cabeçalho x-api-key e devolve um JWT. (O APID encaminha este pedido para o AuthD, pelo que um único URL de base abrange tudo.)

Esta chamada, bem como todas as chamadas baseadas no respetivo resultado, é executada num servidor.

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

Resposta:

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

expiresIn é o tempo de vida restante do token, em segundos; expiresAt representa o mesmo momento como timestamp ISO 8601. Ambos são derivados da claim de expiração do próprio token, pelo que um token emitido sem essa claim é devolvido apenas como { "token": "…" } — leia estes valores de forma defensiva e, caso não estejam presentes, utilize a sua própria margem conservadora. Utilize qualquer um deles para agendar a troca seguinte; não codifique uma duração fixa.

Para plataformas com suporte nativo para OAuth2, crie um cliente OAuth na Conta de serviço em vez de uma chave de API, ou em conjunto com esta. O ID e o segredo do cliente são apresentados uma única vez durante a criação.

Peça um token ao endpoint de tokens da Conta de serviço através da concessão client_credentials padrão — são aceites tanto client_secret_post (campos de formulário) como 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"

Resposta (resposta de token OAuth2 padrão):

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

O token resultante tem exatamente a mesma estrutura e os mesmos direitos que um token obtido através da troca de uma chave de API — utilize-o da mesma forma. Se o seu middleware pedir um “URL do token”, utilize o endpoint acima.

Envie o token em todas as chamadas à API principal:

Authorization: Bearer <token>

Uma forma rápida de confirmar que o token funciona:

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

Os pedidos sem um token válido recebem 401 com o corpo { "code": "UNAUTHORIZED", "message": "...", "status": 401 } — consulte Convenções dos pedidos para conhecer o contrato de erros e Erros e resultados das digitalizações para ver a lista completa de códigos.

Os tokens bearer das Contas de serviço são de curta duração — atualmente, 15 minutos — mas deve sempre ler a duração na resposta (expiresIn/expiresAt na troca da chave, expires_in na concessão OAuth), em vez de a codificar. Não existe um refresh token: quando um token expirar, volte a trocar a credencial.

Um cliente robusto combina ambos os padrões — renova de forma proativa com uma margem de segurança e trata uma ocorrência de 401 como um sinal para renovar e repetir o pedido (isto também abrange desvios do relógio e revogações durante o período de validade):

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

A troca tem um custo reduzido; não crie caches prolongadas em torno desta operação. A curta duração também simplifica a resposta a incidentes: a revogação de uma credencial impede imediatamente a emissão de novos tokens, e qualquer token já emitido expira ao fim de poucos minutos.

Uma Conta de serviço autentica o sistema; não permite à DUST saber que pessoa premiu o botão no seu ERP ou nas suas instalações de produção. Se pretender essa rastreabilidade, declare-a em cada pedido através do cabeçalho Dust-Ctx-Declared-Actor — um pequeno objeto JSON:

Dust-Ctx-Declared-Actor: {"id": "JDOE", "system": "SAP", "displayName": "Jane Doe"}
  • id é obrigatório; system, displayName e role são opcionais. O valor pode ser codificado como URI (obrigatório se contiver caracteres não ASCII) e não pode exceder 1 KB.
  • O autor declarado é registado textualmente em todos os eventos escritos pelo pedido e apresentado no histórico de atividades como atribuição declarada — é fornecido pela sua integração, não é verificado pela DUST e nunca concede nem restringe permissões.
  • Um administrador da organização pode definir a política de atribuição de uma Conta de serviço como obrigatória; nesse caso, os pedidos de escrita sem um autor declarado são rejeitados com 403 ATTRIBUTION_REQUIRED.

A API verifica a assinatura de cada token bearer através do JSON Web Key Set do AuthD e verifica as claims de emissor (https://authd.dustid.io/api/auth em produção) e de audiência. Normalmente, não precisa de conhecer este detalhe — mas, se o seu próprio backend precisar de verificar JWTs emitidos pela DUST (por exemplo, para confiar num token encaminhado por outro serviço interno), o JWKS é público:

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

Devolve um documento padrão { "keys": [ ... ] }, utilizável com qualquer biblioteca JOSE.

Uma integração de digitalização no browser, do início ao fim

Seção intitulada “Uma integração de digitalização no browser, do início ao fim”

O padrão abaixo é o que todas as integrações de digitalização em browsers ou dispositivos móveis devem seguir. Dois ficheiros, dois locais de execução, uma credencial — que nunca sai do segundo ficheiro.

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

O seu proxy também é o local natural para aplicar regras por utilizador que a API DUST não pode conhecer: em que Equipas este funcionário pode pesquisar, se pode vincular além de identificar e que informações são registadas.

Uma Conta de serviço pode ter várias credenciais ativas em simultâneo, pelo que a substituição não requer qualquer interrupção:

  1. Crie uma chave de substituição (ou um cliente OAuth) na mesma Conta de serviço.
  2. Implemente-a na sua aplicação (ambas as credenciais funcionam durante o período de sobreposição).
  3. Confirme que o tráfego de produção utiliza a nova credencial — a hora da última utilização de cada chave está visível no portal.
  4. Revogue a credencial antiga.