Passkeys (WebAuthn)
L’Auth Relay courtise les cérémonies WebAuthn / passkey pour chaque tenant
(signé HMAC #1). Il valide l’origine du navigateur contre l’allowlist du tenant,
puis transmet à auth_api le webauthn_rp_id et le rp_name du tenant — les
credentials sont ainsi partitionnés par tenant.
Configuration du tenant
Section intitulée « Configuration du tenant »Deux champs (posés au provisionnement ou via /update/tenant) activent les
passkeys :
webauthn_rp_id— le Relying Party ID, l’eTLD+1 de l’origine web du tenant (ex.example.com). Requis ; ne peut pas franchir une frontière eTLD+1 (example.comcouvreapp.example.commais pasexample.net).webauthn_origins— l’allowlist d’origines autorisées à exécuter une cérémonie. Liste vide = passkeys désactivées.
Si webauthn_rp_id est absent ou l’origine hors allowlist, les routes de
cérémonie renvoient 403.
Les endpoints
Section intitulée « Les endpoints »Quatre routes de cérémonie (paire options/verify) et trois routes de gestion des credentials.
| Méthode | Chemin | Rôle |
|---|---|---|
| POST | /relay/webauthn/register/options | Démarrer l’enrôlement (creation options). |
| POST | /relay/webauthn/register/verify | Finaliser l’enrôlement (attestation). |
| POST | /relay/webauthn/authenticate/options | Démarrer la connexion (request options). |
| POST | /relay/webauthn/authenticate/verify | Finaliser la connexion (assertion). |
| POST | /relay/webauthn/fetch/passkeys | Lister les passkeys de l’utilisateur. |
| POST | /relay/webauthn/rename/passkey | Renommer un passkey. |
| POST | /relay/webauthn/revoke/passkey | Révoquer (soft-delete) un passkey. |
Démarrer un enrôlement
Section intitulée « Démarrer un enrôlement »browser_origin doit être dans webauthn_origins. relay_user_linked_id est
l’identifiant côté relais de l’utilisateur (24 caractères).
curl $BASE_URL/api/v1/relay/webauthn/register/options \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "relay_user_linked_id": "652f1f77bcf86cd799439011", "username": "jane@example.com", "display_name": "Jane Doe", "browser_origin": "https://example.com" }'Le data renvoyé contient les PublicKeyCredentialCreationOptions à passer à
navigator.credentials.create(). Renvoyez ensuite le session_id et
l’attestation à register/verify.
curl $BASE_URL/api/v1/relay/webauthn/register/verify \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "session_id": "sess_abcdef012345", "attestation": { "id": "...", "rawId": "...", "type": "public-key", "response": {} }, "nickname": "Mon ordinateur", "browser_origin": "https://example.com" }'Connexion par passkey
Section intitulée « Connexion par passkey »Symétrique : authenticate/options puis authenticate/verify. À l’étape
options, relay_user_linked_id est optionnel — omettez-le pour un flux sans
nom d’utilisateur (credential découvrable).
curl $BASE_URL/api/v1/relay/webauthn/authenticate/options \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "browser_origin": "https://example.com" }'Gérer les credentials
Section intitulée « Gérer les credentials »curl $BASE_URL/api/v1/relay/webauthn/fetch/passkeys \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "relay_user_linked_id": "652f1f77bcf86cd799439011" }'curl $BASE_URL/api/v1/relay/webauthn/rename/passkey \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "passkey_id": "pk_abc123def456", "relay_user_linked_id": "652f1f77bcf86cd799439011", "nickname": "Ordinateur du travail" }'curl $BASE_URL/api/v1/relay/webauthn/revoke/passkey \ -X POST \ -H "X-Bloonio-Tenant-Id: tnt_..." \ -H "X-Bloonio-Timestamp: 1718966400000" \ -H "X-Bloonio-Signature: <hex_hmac_sha256>" \ -H "Content-Type: application/json" \ -d '{ "passkey_id": "pk_abc123def456", "relay_user_linked_id": "652f1f77bcf86cd799439011" }'Fichiers d’association .well-known
Section intitulée « Fichiers d’association .well-known »Les passkeys natifs (Android/iOS) exigent que l’app soit déclarée dans les
fichiers d’association de la Relying Party bloonio.com. Le relais sert
lui-même /.well-known/assetlinks.json (Android) et
/.well-known/apple-app-site-association (iOS) ; Caddy route les chemins de
l’apex bloonio.com vers le relais.
Onboarding d’une app
Section intitulée « Onboarding d’une app »Ajouter une app à la RP bloonio.com est une étape formelle d’onboarding, en
résumé :
- Empreinte Android — le SHA-256 de la clé de signature d’app (Play Console → App signing), pas la clé d’upload ni un keystore local.
- Identité iOS — Team ID et bundle id canonique, plus l’entitlement
Associated Domains
webcredentials:bloonio.com. - Compléter l’entrée dans
passkey_identities.yaml; passer la section àenabled: trueseulement quand chaque valeur est réelle. - Régénérer (
python wellknown/generate_wellknown.py) et committer le YAML etgenerated/ensemble (la CI l’impose). - Déployer le relais — les fichiers sont livrés avec le service.
- Appliquer les origines tenant sur l’hôte :
./bash/install/apply-passkey-origins.py prod --tenant-id tnt_…. Sans cette étape, les assertions Android natives sont rejetées même avec des.well-knownvalides. - Valider :
./wellknown/validate_wellknown.sh(avec--googlepour interroger l’API Digital Asset Links), à re-exécuter après tout changement Cloudflare.
Déploiement progressif (passkeys_enabled)
Section intitulée « Déploiement progressif (passkeys_enabled) »Le drapeau passkeys_enabled par tenant pilote un déploiement progressif sans
jamais verrouiller personne :
| Valeur | Effet |
|---|---|
null (défaut) | Activé quand configuré (webauthn_rp_id + webauthn_origins). Tenants vivants inchangés. |
false | Enrôlement et connexion refusés (403) — mais la gestion reste disponible. |
true | Identique à null (activation explicite). |
Séquence recommandée : onboarder le tenant, le tenir en pause
(passkeys_enabled: false) pendant un test interne, puis activer
(passkeys_enabled: true). Repli instantané avec {"passkeys_enabled": false}.
Métriques
Section intitulée « Métriques »Aucun backend de métriques n’est câblé : le relais émet des lignes de log
analysables au point de passage multi-tenant
(app/modules/webauthn/router.py), à agréger via Loki / CloudWatch / grep :
passkey.metric event=<enroll|signin|revoke> outcome=<ok|fail> tenant=<tnt_…> [reason=<…>]Les échecs côté client (cancelled, unsupported, no_credential)
n’atteignent jamais le serveur — les SDK les exposent comme des erreurs typées ;
branchez-les sur votre propre analytique si vous avez besoin des taux
d’annulation.