Passkeys (WebAuthn)
Connexion sans mot de passe et résistante au phishing avec les passkeys FIDO2 : enregistrement, connexion sans identifiant et gestion en libre-service.
Un passkey est un credential FIDO2 / WebAuthn : une paire de clés créée par l'appareil de l'utilisateur (Touch ID, Windows Hello, une clé de sécurité, un téléphone). La clé privée ne quitte jamais l'authentificateur ; Obexal ne conserve que la clé publique. Les passkeys résistent au phishing par construction, car le navigateur lie cryptographiquement chaque assertion au domaine de connexion.
Le fonctionnement des passkeys dans Obexal
Toute opération WebAuthn est une cérémonie en deux temps :
- begin : le serveur renvoie les options WebAuthn (
publicKey) et unstateTokenopaque. L'état de la cérémonie est conservé côté serveur pendant 5 minutes, indexé par le hachage du token. - finish : le navigateur produit un
PublicKeyCredential, que vous renvoyez avec lestateToken. Le token est à usage unique : unfinishrejoué ou expiré échoue de façon générique.
Les vérifications de signature, de challenge, d'origine et d'attestation sont déléguées à la bibliothèque éprouvée go-webauthn ; rien de cryptographique n'est réimplémenté.
Prérequis
- HTTPS : les navigateurs n'exposent l'API WebAuthn qu'en contexte sécurisé (
https://, oulocalhosten développement). - Un navigateur moderne : les versions actuelles de Chrome, Edge, Firefox et Safari prennent en charge WebAuthn.
- Tous les appels
POST /v1/...exigent le jeton CSRF en double soumission (récupérez-le viaGET /v1/csrf).
Un passkey est lié au domaine de connexion (le Relying Party ID WebAuthn). Les exemples ci-dessous utilisent accounts.obexal.com ; si votre organisation se connecte sur un domaine personnalisé, remplacez-le.
Enregistrer un passkey
L'enregistrement exige une session authentifiée (l'utilisateur ajoute un passkey à un compte existant).
# 1. Begin : récupérer les options de création (cookie de session + CSRF).
curl -sS -X POST https://accounts.obexal.com/v1/webauthn/register/begin \
-b cookies.txt -H "X-CSRF-Token: $CSRF"
# 200 -> {"publicKey":{"authenticatorSelection":{"residentKey":"required",
# "userVerification":"preferred"},"excludeCredentials":[...],...},
# "stateToken":"Q2VyZW1vbnk..."}Les options exigent une resident key (credential discoverable) et demandent userVerification: preferred. excludeCredentials liste les passkeys déjà enregistrés, pour que le navigateur refuse les doublons. Passez l'objet publicKey à navigator.credentials.create(), puis finalisez :
# 2. Finish : renvoyer le PublicKeyCredential du navigateur avec le stateToken.
curl -sS -X POST https://accounts.obexal.com/v1/webauthn/register/finish \
-b cookies.txt -H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' \
-d '{"stateToken":"Q2VyZW1vbnk...","name":"MacBook Touch ID","credential":{...}}'
# 201 -> {"credential":{"id":"0f1e2d...","name":"MacBook Touch ID","createdAt":"..."}}name est un libellé optionnel (64 caractères maximum) affiché dans la liste des passkeys.
Se connecter avec un passkey
La connexion est sans identifiant (usernameless) : aucun e-mail n'est demandé. Le credential discoverable porte l'identité de l'utilisateur (userHandle), et le serveur la relie au compte.
# 1. Begin : aucune authentification, corps vide. Limité en débit par IP.
curl -sS -X POST https://accounts.obexal.com/v1/webauthn/login/begin \
-H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' -d '{}'
# 200 -> {"publicKey":{"challenge":"...","userVerification":"preferred",
# "allowCredentials":[]},"stateToken":"..."}Passez publicKey à navigator.credentials.get() (la médiation conditionnelle donne l'expérience d'autocomplétion du navigateur), puis :
# 2. Finish : en cas de succès, le cookie de session est posé.
curl -sS -X POST https://accounts.obexal.com/v1/webauthn/login/finish \
-H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' \
-d '{"stateToken":"...","credential":{...}}'
# 200 -> {"user":{"id":"...","email":"a@b.eu","status":"active",...}}Les échecs (assertion invalide, stateToken inconnu ou consommé, credential introuvable) renvoient un 401 invalid_credentials générique.
Un facteur primaire fort
Une connexion par passkey ouvre directement la session : aucun défi MFA supplémentaire n'est empilé par-dessus. C'est un choix délibéré. Un passkey combine déjà la possession de l'appareil et la vérification de l'utilisateur, et il résiste au phishing : il vaut au moins un mot de passe plus un second facteur. Les connexions par mot de passe, par e-mail sans mot de passe et sociales passent en revanche toutes par le point de passage MFA.
Une nuance, par honnêteté : Obexal demande userVerification: preferred, pas required. L'authentificateur est invité à vérifier l'utilisateur (biométrie ou code PIN) chaque fois qu'il le peut, mais une assertion produite sans vérification de l'utilisateur n'est pas rejetée.
Gérer ses passkeys
Chaque utilisateur gère ses propres passkeys (session requise) :
curl -sS -b cookies.txt https://accounts.obexal.com/v1/webauthn/credentials{ "credentials": [ { "id": "0f1e2d...", "name": "MacBook Touch ID", "createdAt": "2026-06-14T10:00:00Z", "lastUsedAt": "2026-06-14T12:30:00Z" } ] }lastUsedAt reste null tant que le passkey n'a jamais servi à se connecter. Pour en supprimer un :
curl -sS -b cookies.txt -X POST https://accounts.obexal.com/v1/webauthn/credentials/delete \
-H "X-CSRF-Token: $CSRF" -H 'Content-Type: application/json' \
-d '{"credentialId":"0f1e2d..."}'
# 204La suppression est scopée à l'utilisateur de la session : personne ne peut supprimer le passkey d'un autre compte par cet endpoint.
Notes de sécurité
- Détection de clonage : à chaque connexion, le compteur de signatures de l'authentificateur est contrôlé. Une régression du compteur (authentificateur potentiellement cloné) est consignée dans le journal d'audit. Elle ne bloque pas la connexion, car les passkeys synchronisés renvoient légitimement un compteur à zéro, mais elle vous laisse une trace forensique.
- Cérémonies à usage unique : seul le hachage de chaque
stateTokenest stocké, avec un TTL de 5 minutes, et il est consommé atomiquement aufinish. - Limitation de débit :
login/beginetlogin/finishsont limités en débit par IP. - L'enregistrement, la connexion, les alertes de clonage et les suppressions sont tous écrits dans le journal d'audit.