Image de couverture : Passkeys hybrides dans Symfony 7.4 : gérer la transition vers le sans-mot-de-passe
tech

Passkeys hybrides dans Symfony 7.4 : gérer la transition vers le sans-mot-de-passe

19 March 2026
6 min de lecture
1 vues
Sébastien Muler

Passkeys hybrides dans Symfony 7.4 : gérer la transition vers le sans-mot-de-passe

L'authentification sans mot de passe représente une avancée majeure en matière de sécurité web. Pourtant, migrer une application existante vers un modèle 100 % passwordless reste un défi considérable : utilisateurs habitués aux gestionnaires de mots de passe, environnements d'entreprise sans clés de sécurité matérielles, appareils non compatibles WebAuthn... La réalité du terrain impose une approche progressive.

C'est précisément l'objet de cet article : construire un modèle hybride dans Symfony 7.4, capable de proposer l'authentification par passkey quand elle est disponible, tout en maintenant un fallback par mot de passe traditionnel pour les utilisateurs qui ne sont pas encore prêts.

Cet article s'inspire de l'excellent travail de Matt Mochalkin publié sur dev.to, que nous adaptons et enrichissons ici pour la communauté MulerTech.


Pourquoi une stratégie hybride plutôt qu'une migration brutale ?

Passer directement à un système entièrement passwordless peut sembler séduisant techniquement, mais cela génère des frictions pour vos utilisateurs. Voici les principaux freins rencontrés en production :

  • Les utilisateurs legacy font confiance à leurs gestionnaires de mots de passe depuis des années.
  • Les environnements corporate n'ont pas encore standardisé l'usage des clés de sécurité FIDO2.
  • La compatibilité navigateur/appareil reste hétérogène, même en 2024.

La stratégie hybride permet de :

  1. Proposer automatiquement la biométrie ou la passkey si l'utilisateur en possède une.
  2. Afficher un formulaire de mot de passe classique dans le cas contraire.
  3. Encourager progressivement l'enregistrement d'une passkey sans forcer la main.

C'est la phase de transition intelligente, pas un compromis sur la sécurité.


La stack technique recommandée

Pour mettre en œuvre cette approche, voici les composants nécessaires :

  • PHP 8.2+ : pour profiter des readonly classes et de la promotion de constructeur.
  • Symfony 7.4 : avec les dernières améliorations du composant Security.
  • web-auth/webauthn-symfony-bundle : le bundle de référence pour l'intégration WebAuthn/passkeys en Symfony.
  • Stimulus & AssetMapper : pour une expérience frontend sans Node.js ni bundler complexe.

L'installation du bundle WebAuthn se fait via Composer :

composer require web-auth/webauthn-symfony-bundle

Ce bundle expose les services nécessaires pour créer des challenges WebAuthn, valider les assertions et persister les authenticators en base de données.


Architecture du formulaire de connexion hybride

Détection automatique de la passkey

L'idée centrale est d'avoir un seul formulaire de connexion intelligent. Lorsque l'utilisateur saisit son identifiant (email), le frontend interroge silencieusement le serveur pour savoir si ce compte possède une passkey enregistrée.

// Stimulus Controller - login_controller.js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static targets = ['email', 'passwordBlock', 'passkeyBlock']

  async checkCredentials() {
    const email = this.emailTarget.value
    if (!email) return

    const response = await fetch(`/auth/check-passkey?email=${encodeURIComponent(email)}`)
    const data = await response.json()

    if (data.hasPasskey) {
      this.passkeyBlockTarget.classList.remove('hidden')
      this.passwordBlockTarget.classList.add('hidden')
      this.triggerConditionalMediation()
    } else {
      this.passwordBlockTarget.classList.remove('hidden')
      this.passkeyBlockTarget.classList.add('hidden')
    }
  }

  async triggerConditionalMediation() {
    // WebAuthn Conditional UI (Passkey Autofill)
    if (!window.PublicKeyCredential?.isConditionalMediationAvailable) return
    const available = await PublicKeyCredential.isConditionalMediationAvailable()
    if (!available) return
    // Lancer le challenge WebAuthn avec mediation: 'conditional'
    // ...
  }
}

Côté Symfony, un endpoint léger vérifie la présence d'un authenticator pour l'email donné :

// src/Controller/AuthCheckController.php
#[Route('/auth/check-passkey', name: 'auth_check_passkey')]
public function checkPasskey(
    Request $request,
    AuthenticatorRepository $authenticatorRepository,
    UserRepository $userRepository
): JsonResponse {
    $email = $request->query->getString('email');
    $user = $userRepository->findOneBy(['email' => $email]);

    if (!$user) {
        return $this->json(['hasPasskey' => false]);
    }

    $hasPasskey = $authenticatorRepository->countByUser($user) > 0;

    return $this->json(['hasPasskey' => $hasPasskey]);
}

Le Conditional Mediation : l'autofill des passkeys

La Conditional Mediation (ou Passkey Autofill) est l'une des UX les plus puissantes de WebAuthn. Elle permet au navigateur de suggérer automatiquement une passkey dans le champ email, comme il le ferait avec un mot de passe sauvegardé, sans aucune interaction supplémentaire de l'utilisateur.

Pour l'activer, deux conditions sont nécessaires :

  1. L'input HTML doit contenir l'attribut autocomplete="username webauthn" :
<input
  type="email"
  name="email"
  autocomplete="username webauthn"
  data-login-target="email"
  data-action="blur->login#checkCredentials"
/>
  1. Lancer le challenge WebAuthn avec mediation: 'conditional' dès le chargement de la page, avant toute interaction utilisateur.

Cette approche réduit considérablement le nombre de clics nécessaires pour se connecter et améliore l'adoption des passkeys de façon organique.


Sécurité du modèle hybride : ce qu'il ne faut pas négliger

Un modèle hybride ne doit pas devenir un modèle affaibli. Quelques règles essentielles :

Éviter la dégradation forcée : Ne jamais permettre à un utilisateur ayant déjà une passkey de contourner WebAuthn pour se connecter uniquement par mot de passe, sans passer par une vérification d'identité supplémentaire (ex. email OTP).

Logger les tentatives de fallback : Tout recours au mot de passe pour un compte passkey-enabled doit être journalisé et potentiellement alerté.

Encourager l'enregistrement progressif : Après chaque connexion par mot de passe réussie, afficher une bannière invitant l'utilisateur à enregistrer une passkey. Le bundle WebAuthn fournit les routes et services nécessaires pour cette étape.

// Vérification dans le SecurityListener
if ($user->hasPasskeys() && !$this->isWebAuthnAuthenticated($request)) {
    $this->logger->warning('Passkey bypass attempt', ['user' => $user->getId()]);
}

Conclusion

La migration vers les passkeys n'est pas un interrupteur que l'on bascule du jour au lendemain. C'est un processus qui demande de respecter le rythme de vos utilisateurs tout en maintenant un niveau de sécurité élevé. Le modèle hybride décrit dans cet article offre exactement cet équilibre : une expérience moderne pour ceux qui sont prêts, un fallback sécurisé pour les autres.

Avec Symfony 7.4, le bundle web-auth/webauthn-symfony-bundle et Stimulus, l'implémentation est à la portée de toute équipe PHP expérimentée. La Conditional Mediation, en particulier, est un levier UX sous-exploité qui peut significativement accélérer l'adoption des passkeys dans votre base utilisateurs.

La prochaine étape ? Mettre en place une politique de dépréciation des mots de passe avec une date butoir communiquée à vos utilisateurs — et planifier le passage au tout-passkey en toute sérénité.

Partager cet article