ApiBorne
Sommaire du guide

Authentification

Deux headers sur toutes les requêtes, deux 401 typés, et un impératif souvent oublié : la borne est une application navigateur, votre API doit répondre aux preflights CORS.

Les deux headers

headers requis
X-Kiosk-Auth-Key: <clé partagée>       ← « Clé d'autorisation compte » (admin ApiBorne, page Connectivité)
X-Kiosk-Device-Id: <id du device>       ← « Id de l'appareil » (admin ApiBorne, page Bornes)

La clé identifie le compte (elle est commune à toutes les bornes du compte), le device id identifie quelle borne appelle — c'est lui qui permet de révoquer une borne isolément.

Où trouver la clé et les identifiants

  • La clé partagée est la « Clé d'autorisation compte » affichée dans l'admin ApiBorne, page Connectivité (champ copiable). C'est cette valeur que chaque borne envoie dans X-Kiosk-Auth-Key — vous la stockez chez vous telle quelle, à l'installation de l'intégration. C'est aussi elle qui sert de header Authorization pour vos appels sortants vers le serveur ApiBorne.
  • Les identifiants de devices sont les « Id de l'appareil » de la page Bornes de l'admin ApiBorne (ex. HfDXSQ0dv). La flotte est gérée là-bas ; chaque borne transmet son id tel quel dans X-Kiosk-Device-Id.
Deux politiques possibles côté éditeur : tolérante (recommandée — la flotte étant gérée dans l'admin ApiBorne, vous n'avez aucun référentiel de devices à dupliquer : un device inconnu présentant une clé valide est enregistré au premier contact) ou stricte (vous provisionnez la liste des devices et rejetez les inconnus — utile si vous voulez pouvoir révoquer une borne isolément de votre côté). L'implémentation de référence propose les deux, commutables par un réglage.

Implémenter la vérification, pas à pas

Activité : preflight OPTIONS, vérification du device puis de la clé, traitement.Requête entranteOPTIONS ?nonRépondre au preflight CORSsans exiger les headers d'authouiDevice connu ?X-Kiosk-Device-Idnon401 UNKNOWN_DEVICEcorps d'erreur normaliséouiClé valide ?X-Kiosk-Auth-Keynon401 INVALID_AUTH_KEYouiTraiter l'opération
L'ordre de validation est contractuel : OPTIONS sans auth, device d'abord, clé ensuite — chaque refus a son code.

L'ordre est contractuel — à chaque requête entrante :

  1. requête OPTIONS ? → répondre au preflight CORS (voir plus bas), sans exiger les headers d'auth ;
  2. X-Kiosk-Device-Id absent ou inconnu → 401 avec le code UNKNOWN_DEVICE ;
  3. X-Kiosk-Auth-Key absente ou différente de la clé attendue → 401 avec le code INVALID_AUTH_KEY ;
  4. sinon → traiter l'opération.
typescript
// src/server/contract/auth.ts (implémentation de référence) — extrait
export function requireKioskAuth(request: NextRequest): NextResponse | null {
  // 1. Le device D'ABORD (ordre contractuel)
  const deviceId = request.headers.get("x-kiosk-device-id");
  if (!deviceId) {
    return contractError("UNKNOWN_DEVICE", "Missing X-Kiosk-Device-Id header");
  }
  if (!isKnownDevice(deviceId)) {
    return contractError("UNKNOWN_DEVICE", `Unknown kiosk device '${deviceId}'`);
  }
  // 2. Puis la clé partagée
  return requireAuthKey(request);
}

export function requireAuthKey(request: NextRequest): NextResponse | null {
  const authKey = request.headers.get("x-kiosk-auth-key");
  if (!authKey || authKey !== expectedKey()) {
    return contractError("INVALID_AUTH_KEY", "Invalid authorization key for this device");
  }
  return null; // authentifié
}

Et dans chaque handler, la garde s'appelle en première ligne :

typescript
// Dans CHAQUE handler du contrat : la garde en première ligne
export const POST = withErrorBoundary(async (request: NextRequest) => {
  const authError = requireKioskAuth(request);
  if (authError) return authError;   // 401 typé, corps normalisé

  // … logique métier de l'opération
});

Voir le fichier complet sur GitHub → (avec la politique tolérante et le header X-Kiosk-Office-Id commentés).

Tester vos 401 avec curl

Sans headers — le device est vérifié en premier :

bash
curl -sS -i "$BASE/patients/identify" -H 'Content-Type: application/json' -d '{"criteria":{}}'
# HTTP/1.1 401 Unauthorized
# { "error": { "code": "UNKNOWN_DEVICE", "message": "Missing X-Kiosk-Device-Id header", "details": null } }

Device connu mais mauvaise clé :

bash
curl -sS -i "$BASE/patients/identify" \
  -H 'X-Kiosk-Device-Id: KIOSK-042' -H 'X-Kiosk-Auth-Key: mauvaise-cle' \
  -H 'Content-Type: application/json' -d '{"criteria":{}}'
# HTTP/1.1 401 Unauthorized
# { "error": { "code": "INVALID_AUTH_KEY", "message": "Invalid authorization key for this device", "details": null } }

Appel authentifié :

bash
curl -sS "$BASE/patients/identify" \
  -H 'X-Kiosk-Device-Id: KIOSK-042' -H 'X-Kiosk-Auth-Key: s3cr3t-key' \
  -H 'Content-Type: application/json' -d '{"criteria":{"lastName":"DURAND","firstName":"MARIE","birthDate":"1980-05-12"}}'
# HTTP/1.1 200 OK — { "patients": [ … ] }
Un 401 ne doit jamais être transformé en redirection (302 vers une page de login) : la borne attend les statuts HTTP réels et le corps d'erreur normalisé { "error": { code, message, details } }.

CORS obligatoire

La borne appelle votre serveur depuis un navigateur. Vous devez répondre aux preflights OPTIONS avec :

réponse preflight
Access-Control-Allow-Origin: <origine de la borne>
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Kiosk-Auth-Key, X-Kiosk-Device-Id

Module de référence : src/server/contract/cors.ts.

Multi-sites : X-Kiosk-Office-Id

ApiBorne envoie aussi X-Kiosk-Office-Id sur chaque appel : un éditeur multi-établissements sélectionne ainsi le bon site sans avoir à le résoudre depuis sa table de devices. Sa valeur est l'identifiant libre saisi dans la configuration d'intégration ApiBorne — vous choisissez son format à l'installation. Un éditeur mono-site peut simplement l'ignorer.

Exceptions : appels du serveur ApiBorne

Deux opérations sont appelées par le serveur ApiBorne (Cockpit), pas par une borne : POST /staff/sign-in et PUT /appointments/{id}/status. Elles ne portent que X-Kiosk-Auth-Key — pas de device id : votre garde doit prévoir les deux niveaux (requireKioskAuth complet vs requireAuthKey seul).