Cet article fait partie d’une série consacrée au site public rezopouce.fr. Après avoir présenté l’architecture générale de l’écosystème et le socle géographique du site, ce volet aborde le domaine le plus complexe du projet : la gestion des utilisateurs.

Le problème

Sur le site public de Rezo Pouce, un utilisateur n’existe pas à un seul endroit. Il existe en local, dans la base Symfony du site, et en distant, dans la base de l’API centrale. Ces deux représentations doivent rester synchronisées à tout moment — à l’inscription, à la connexion, à la réinitialisation du mot de passe. Ce système dual est la conséquence directe de l’architecture de la plateforme : le site public n’accède jamais directement à la base de données principale, il passe systématiquement par l’API.

Cette contrainte architecturale, saine du point de vue de la séparation des responsabilités, complexifie significativement tout ce qui touche à la gestion des utilisateurs.

L’authentification duale

L’authentification locale repose sur FOSUserBundle, le bundle historique de gestion des utilisateurs dans l’écosystème Symfony. L’entité utilisateur locale étend le modèle FOSUser standard — identifiant, email, mot de passe encodé, rôles — et lui ajoute deux champs spécifiques : un identifiant correspondant sur l’API distante, et un token d’authentification API.

Le flux de connexion illustre bien cette dualité. Quand un utilisateur soumet le formulaire de connexion, FOSUserBundle l’authentifie d’abord contre la base locale. Si la vérification réussit, un listener Symfony intercepte l’événement de connexion et déclenche un appel à l’API pour obtenir un token d’authentification distant. Ce token est stocké dans l’entité utilisateur locale et sera utilisé pour tous les appels API ultérieurs pendant la session.

Mais la connexion ne s’arrête pas là. Une étape de vérification post-connexion contrôle que l’utilisateur possède bien un profil Séniors valide côté API. Si ce n’est pas le cas — par exemple si le profil a été désactivé depuis le back-office — l’utilisateur est immédiatement déconnecté. Cette vérification est invisible pour l’utilisateur quand tout va bien, mais elle garantit que seuls les utilisateurs légitimes accèdent à l’espace Séniors.

L’inscription en quatre étapes

L’inscription est le processus le plus complexe du site. Elle est spécifiquement conçue pour le service Rezo Séniors et se présente sous la forme d’un formulaire AngularJS en quatre étapes, intégré dans une page Symfony.

Identification

La première étape collecte les identifiants : nom d’utilisateur, email, mot de passe. L’unicité de l’identifiant et de l’email est vérifiée en temps réel auprès de l’API à chaque perte de focus sur le champ. Si un doublon est détecté, un panneau propose la récupération d’un compte existant plutôt que la création d’un nouveau. Le mot de passe est soumis à une politique stricte : au moins huit caractères, avec majuscule, minuscule, chiffre et caractère spécial.

Profil

La deuxième étape recueille les informations personnelles : prénom, nom, date de naissance, genre, et type d’utilisation — conducteur, passager, ou les deux. La date de naissance n’est pas qu’une simple donnée de profil : elle détermine l’âge de l’utilisateur, et un mineur se voit refuser l’option conducteur tout en se voyant imposer des documents supplémentaires à l’étape de validation.

Coordonnées et éligibilité

C’est l’étape pivot. L’utilisateur renseigne son adresse et sa commune de résidence. La commune est sélectionnée via un widget d’autocomplétion qui interroge la base de données. Si la commune n’est pas éligible au dispositif Rezo Séniors, le formulaire affiche un message clair et oriente l’utilisateur vers l’inscription Rezo Pouce classique.

Pour les communes éligibles, une sous-section Rezo Séniors apparaît : inscription automatique comme passager sénior, possibilité de s’inscrire comme conducteur solidaire, téléchargement obligatoire de la charte correspondante, et acceptation des CGU du service.

Validation

La dernière étape exige les pièces justificatives : une copie de pièce d’identité (photo ou scan, jusqu’à 10 Mo, en PDF ou image), et pour les mineurs, une autorisation parentale accompagnée de la pièce d’identité du responsable légal. Les fichiers sont uploadés directement vers l’API. L’utilisateur choisit éventuellement de s’abonner à la newsletter, et doit télécharger puis accepter les CGU — la case d’acceptation ne devient cliquable qu’une fois le document effectivement téléchargé.

Le mécanisme d’utilisateur temporaire

Si le parcours d’inscription est aussi exigeant, c’est parce que Rezo Pouce en a besoin pour fonctionner. Le service repose sur une promesse de sécurité : chaque utilisateur est identifié, une assurance de bonne fin couvre les trajets, et pour que cette assurance puisse être mise en œuvre, le service doit savoir précisément qui sont les personnes inscrites. La pièce d’identité, la validation de l’adresse, l’acceptation de la charte — tout cela n’est pas de la lourdeur administrative gratuite. C’est ce qui permet aux utilisateurs de pratiquer l’autostop en confiance, et ce qui permet à Rezo Pouce de garantir cette confiance.

Mais cette exigence a un corollaire technique : à l’issue du formulaire, l’utilisateur n’existe pas encore réellement. Les données saisies dans les quatre étapes sont envoyées à l’API, qui crée un utilisateur temporaire — un enregistrement intermédiaire, distinct d’un compte définitif. L’utilisateur reçoit ensuite un email de confirmation contenant un lien avec un token de validation.

Quand la confirmation arrive, un service de conversion prend le relais. Il crée l’utilisateur localement dans Symfony, crée l’utilisateur définitif côté API, hydrate le profil distant avec toutes les données collectées (identité, adresse, commune, type d’utilisation, documents, acceptation des CGU), gère l’éventuelle souscription à la newsletter, puis supprime l’utilisateur temporaire. C’est une opération en plusieurs étapes impliquant plusieurs appels API, chacun devant réussir pour que l’inscription aboutisse.

Ce mécanisme existe pour éviter de créer des comptes définitifs pour des utilisateurs qui ne valideront jamais leur email. L’utilisateur temporaire sert de zone tampon. C’est un pattern que l’on retrouve dans beaucoup de systèmes, mais ici il est rendu plus complexe par la nature duale du système — la conversion doit s’effectuer à la fois localement et sur l’API.

La récupération d’inscription

En pratique, des utilisateurs ne terminaient pas la procédure d’inscription. La raison la plus fréquente : il leur manquait une pièce justificative au moment de l’étape de validation — une photo de carte d’identité pas sous la main, un document à scanner. L’utilisateur quittait le formulaire en pensant pouvoir revenir plus tard. Mais côté système, son email et son identifiant étaient déjà enregistrés dans l’utilisateur temporaire. Le principe d’unicité lui interdisait de recommencer l’inscription depuis le début.

Pour résoudre ce problème, nous avons mis en place un processus de récupération d’inscription. L’utilisateur saisit l’email utilisé lors de sa tentative, l’API retrouve l’utilisateur temporaire correspondant et envoie un email contenant un lien de reprise. Au clic, le token est vérifié et le formulaire d’inscription réapparaît pré-rempli avec les données déjà saisies, permettant de reprendre là où il s’était arrêté.

La réinitialisation de mot de passe

La réinitialisation de mot de passe est un autre point où la dualité du système se manifeste. Le flux suit le parcours classique de FOSUserBundle — saisie de l’email, envoi d’un lien, saisie du nouveau mot de passe — mais avec une étape supplémentaire invisible : la synchronisation avec l’API.

Le défi technique est que FOSUserBundle encode le mot de passe avant de déclencher l’événement de complétion. Or l’API a besoin du mot de passe en clair pour mettre à jour son propre enregistrement. La solution passe par deux listeners chaînés sur deux événements distincts : le premier capture le mot de passe en clair dans un attribut temporaire juste avant l’encodage, et le second l’envoie à l’API une fois la réinitialisation locale terminée.

Les rôles et le contrôle d’accès

Le site définit une hiérarchie de rôles adaptée aux différents profils du réseau — de l’utilisateur de base qui accède au service Séniors, au gestionnaire de territoire qui conserve l’accès aux pages de territoires désabonnés, jusqu’au super administrateur capable d’usurper une identité pour le support. Le contrôle d’accès est configuré par pattern d’URL dans la sécurité Symfony : l’espace Séniors exige une authentification complète, et les routes de synchronisation API sont restreintes par adresse IP.

Un cas particulier mérite d’être mentionné : les utilisateurs inscrits depuis l’application mobile suivent un chemin de confirmation distinct. Le lien dans l’email pointe vers une route spécifique du site public qui transmet la validation à un endpoint dédié de l’API. Ce flux parallèle est nécessaire parce que l’application mobile crée les comptes différemment, mais la confirmation par email passe dans tous les cas par le site web — ajoutant encore un cas d’usage au système dual.

Ce que ce projet m’a appris

L’authentification et l’inscription de ce projet illustrent une réalité fréquente dans les systèmes distribués : la complexité ne vient pas de chaque opération prise individuellement, mais de leur coordination. Créer un utilisateur est simple. Le créer à deux endroits, avec un mécanisme temporaire, une confirmation par email, une synchronisation de mot de passe, et des cas limites comme la récupération d’inscription — c’est un tout autre exercice. C’est aussi un travail de surcharge d’un framework existant — FOSUserBundle — dont on pousse le comportement bien au-delà de ce qu’il propose par défaut.

Mais avec le recul, la duplication des utilisateurs entre le site et l’API est l’erreur architecturale majeure de ce projet. Maintenir deux bases synchronisées pour les mêmes utilisateurs, c’est s’exposer en permanence à des incohérences — un mot de passe mis à jour d’un côté mais pas de l’autre, un profil désactivé dans l’API mais toujours actif localement, un utilisateur temporaire jamais nettoyé. Le mécanisme fonctionne, mais il est fragile par nature, et chaque nouveau cas d’usage ajoute une couche de complexité à un système qui n’aurait pas dû en avoir besoin.

Dans une architecture moderne, la réponse est simple : les utilisateurs n’existent qu’à un seul endroit — l’API. Le front, construit avec un framework comme Vue.js, ne gère que l’authentification : il obtient un token auprès de l’API et l’utilise pour chaque requête. Plus de base utilisateur locale, plus de FOSUserBundle, plus de service de conversion, plus de listeners chaînés pour capturer un mot de passe en clair avant encodage. Toute cette mécanique disparaît, remplacée par un flux direct et prévisible.

Je suis parti de rien sur ce projet, et c’est en le construisant — puis en le maintenant pendant cinq ans — que j’ai mesuré le coût réel de cette décision initiale. C’est un apprentissage que je ne reproduirais pas : aujourd’hui, la question de la source de vérité pour les données utilisateur est la première que je tranche, et la réponse est toujours la même — un seul point de persistance, pas deux.

Une question ou un projet ?

Je suis disponible pour en discuter.

Me contacter