Aller au contenu principal

Identifier le joueur sans mot de passe

Connecter chaque joueur de façon anonyme avec Supabase Auth, sans e-mail ni mot de passe.

Objectifs de la séance​

  • comprendre à quoi sert l'authentification et ce qu'est une connexion anonyme ;
  • distinguer les rôles anon et authenticated ;
  • comprendre comment la session du joueur est mémorisée et rafraîchie ;
  • écrire le service authService.ts en évitant de créer plusieurs utilisateurs pour un même joueur ;
  • afficher l'identifiant du joueur sur l'écran d'accueil.

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. dans le tableau de bord Supabase (menu Storage), ouvrir le bucket images-questions et compter les fichiers qu'il contient
  2. lancer l'application, créer un quiz de 3 questions (thème « Mots de passe », niveau « Débutant ») et repérer la question qui affiche une illustration
  3. citer la méthode du client Supabase utilisée dans imagesService.ts pour obtenir l'adresse publique d'une image
📌 Réponse attendue

Notions théoriques​

Pourquoi identifier le joueur ?​

Jusqu'ici, l'application est anonyme au sens strict : le serveur ne sait pas qui joue. Or nous voulons bientôt enregistrer les parties de chaque joueur, calculer son score et afficher un classement. Il faut donc que le serveur sache, à chaque requête, quel joueur la demande. Cette reconnaissance s'appelle l'authentification.

La solution classique (e-mail + mot de passe) demande un formulaire d'inscription, une adresse e-mail valide, un mot de passe à retenir... C'est lourd pour un petit quiz. Supabase propose une alternative : la connexion anonyme.

L'authentification anonyme​

Avec la connexion anonyme, le joueur n'a rien à saisir. L'application demande à Supabase Auth de créer un utilisateur sans e-mail ni mot de passe, identifié seulement par un uuid (un long identifiant unique, par exemple 3f2b8c1e-...). Cet utilisateur est stocké dans le schéma auth de la base (table auth.users), que l'application ne peut pas lire directement.

Application Supabase Auth
| signInAnonymously() ------------------> crée un utilisateur (uuid)
| <---- jeton d'accès (JWT) + jeton de rafraîchissement
|
| (le jeton est mémorisé dans localStorage)
|
| requête SQL + jeton -------------------> auth.uid() = uuid du joueur
À réactiver dans le tableau de bord

Pour des raisons de sécurité, la connexion anonyme est désactivée par défaut sur un projet Supabase. Vous l'activerez dans le TP : Authentication > Sign In / Providers > Allow anonymous sign-ins. Tant qu'elle est désactivée, l'appel signInAnonymously() échoue.

Les rôles anon et authenticated​

Chaque requête envoyée à la base de données s'exécute avec un rôle PostgreSQL :

RôleQuand ?
anonrequête envoyée avec la clé publishable seule (aucune session)
authenticatedrequête envoyée avec le jeton d'un utilisateur connecté, y compris un utilisateur anonyme

Les droits (grant) et les règles RLS (policy ... to anon, authenticated) que vous avez déjà écrits au tutoriel 6 s'appliquent en fonction de ce rôle.

Anonyme ne veut pas dire « anon »

Un joueur connecté anonymement possède le rôle authenticated, comme un utilisateur qui s'est inscrit avec un e-mail. Une règle écrite to authenticated s'applique donc aussi aux anonymes. Si une règle devait un jour être réservée aux comptes « permanents », il faudrait lire dans le jeton l'indicateur is_anonymous. Dans notre application, cela n'a pas d'importance : tous les joueurs sont anonymes.

La session et le jeton​

Après la connexion, Supabase renvoie une session qui contient :

  • un jeton d'accès (un JWT, JSON Web Token) : une preuve d'identité à durée de vie courte (une heure par défaut), signée par Supabase, qui contient l'uuid du joueur ;
  • un jeton de rafraîchissement : il permet d'obtenir un nouveau jeton d'accès sans que le joueur ait quoi que ce soit à faire.

Le client Supabase joint automatiquement le jeton à chaque requête. Côté base de données, la fonction auth.uid() renvoie l'uuid contenu dans ce jeton : c'est ce qui permettra d'écrire des règles du type « chacun ne voit que ses lignes ».

La session est mémorisée dans le localStorage de l'appareil (c'est la configuration faite dans src/lib/supabase.ts au tutoriel 6). Le joueur est donc reconnu au lancement suivant.

Les limites de la connexion anonyme
  • Si le joueur efface les données de son navigateur, désinstalle l'application ou change de téléphone, sa session est perdue : il redevient un nouveau joueur (et l'ancien reste dans la base, orphelin).
  • Un utilisateur anonyme peut être transformé plus tard en compte permanent (ajout d'un e-mail), mais nous ne le ferons pas dans ce cours.
  • Chaque connexion anonyme crée un utilisateur : en production, on limite ces créations (par adresse IP) et on peut ajouter un CAPTCHA pour décourager les robots. C'est un réglage du tableau de bord Supabase.

Ne créer qu'un seul utilisateur : la promesse partagée​

Imaginez que deux écrans demandent la session en même temps au lancement de l'application. Sans précaution, chacun lancerait sa propre connexion anonyme : deux utilisateurs seraient créés pour un seul joueur, et les données de l'un seraient perdues quand la seconde session écraserait la première.

La parade : une variable connexionEnCours qui garde la promesse de la connexion lancée. Tous les appels suivants reçoivent la même promesse, au lieu d'en lancer une nouvelle.

appel 1 : connexionEnCours est vide -> on lance la connexion et on garde sa promesse
appel 2 : connexionEnCours est remplie -> on renvoie la MÊME promesse (aucune connexion en plus)
échec : on vide connexionEnCours -> un nouvel essai sera possible

Rafraîchir le jeton seulement au premier plan​

Sur téléphone, le jeton d'accès doit être renouvelé régulièrement. Tant que l'application est visible, c'est utile ; en arrière-plan, c'est du réseau et de la batterie gaspillés. React Native fournit AppState qui signale quand l'application passe au premier plan ("active") ou en arrière-plan. Le client Supabase propose startAutoRefresh() et stopAutoRefresh() pour suivre ce rythme. Sur le Web, cette précaution n'est pas nécessaire.


TP pour réfléchir et résoudre des problèmes​

Votre mission : connecter chaque joueur anonymement et afficher son numéro sur l'écran d'accueil. Rappel : dans cette série, toute fonctionnalité se développe sur une branche Git dédiée.

Étape 1 — Créer la branche du tutoriel et activer la connexion anonyme​

Dans le terminal de VS Code (dossier quiz-cyber-expo), vérifiez que vous êtes à jour sur main, puis créez la branche du tutoriel 8 :

git switch main
git pull
git switch -c feature/securite

Ensuite, dans le tableau de bord Supabase : Authentication > Sign In / Providers, activez Allow anonymous sign-ins, puis enregistrez.


Bonne pratique - Une branche par fonctionnalité

Tout le travail du tutoriel 8 (connexion, pseudo, fonctions SQL...) se fera sur feature/securite. La branche main reste ainsi toujours dans un état qui fonctionne, et vous fusionnerez le tout par une Pull Request à la fin du tutoriel.

Étape 2 — Ouvrir la session du joueur​

Créez le fichier src/services/authService.ts. La fonction ouvrirSession fait deux choses : elle regarde d'abord si une session est déjà mémorisée sur l'appareil, sinon elle lance la connexion anonyme. Complétez la ligne qui lance la connexion anonyme :


Bonne pratique - Détail technique en console, message simple à l'écran

Le message technique de Supabase part dans console.warn (utile pour vous), mais l'exception levée contient un texte simple pour le joueur. Un utilisateur n'a pas à lire « Anonymous sign-ins are disabled ».

Étape 3 — Partager la promesse de connexion​

Toujours dans authService.ts, ajoutez au-dessus de ouvrirSession la variable connexionEnCours, puis écrivez la fonction exportée assurerSessionAnonyme. En cas d'échec, la promesse doit être oubliée pour qu'un nouvel essai soit possible. Complétez la ligne qui l'oublie :

// Promesse de connexion partagée : si plusieurs écrans demandent la session
// en même temps, UNE SEULE connexion anonyme est lancée (sinon on créerait
// plusieurs utilisateurs anonymes pour le même joueur).
let connexionEnCours: Promise<string> | null = null;

Bonne pratique - Penser au cas où ça échoue

Si on gardait une promesse rejetée dans connexionEnCours, tous les appels suivants recevraient la même erreur, pour toujours : le bouton « Réessayer » ne servirait à rien. Pensez toujours à l'après-échec quand vous mémorisez un résultat.

Étape 4 — Rafraîchir la session seulement au premier plan​

Ouvrez src/lib/supabase.ts. Modifiez l'import de react-native pour ajouter AppState, puis ajoutez à la fin du fichier le bloc ci-dessous. Complétez la ligne qui démarre le rafraîchissement quand l'application redevient active :


Bonne pratique - Ne pas gaspiller les ressources de l'appareil

Un téléphone a une batterie et un forfait de données limités. Une application qui travaille en arrière-plan sans nécessité est vite désinstallée. Arrêter ce qui n'est pas utile est une marque de qualité.

Étape 5 — Un hook pour utiliser la connexion dans un écran​

Créez src/hooks/useSessionAnonyme.ts. Ce hook reprend le patron du tutoriel 7 : l'état est un EtatChargement<string> (chargement, erreur ou succès) et la fonction reessayer relance l'essai. La connexion ne peut pas être annulée comme une requête : on utilise donc un simple drapeau estAnnule. Complétez la ligne qui demande la session :


Bonne pratique - Le drapeau est vérifié avant chaque setState

Si le joueur quitte l'écran pendant la connexion, l'effet est nettoyé (estAnnule = true). Le test if (!estAnnule) évite alors de modifier l'état d'un composant qui n'existe plus.

Étape 6 — Afficher le numéro du joueur sur l'accueil​

Dans src/app/index.tsx, appelez le hook, puis affichez selon l'état : « Connexion... » pendant le chargement, un message d'erreur avec un bouton « Réessayer » en cas d'échec, et en cas de succès le début de l'identifiant puis le bouton « Créer un quiz ». Complétez la ligne qui affiche les 8 premiers caractères de l'identifiant :


Bonne pratique - Ne pas afficher d'identifiant complet

Un uuid complet n'a aucun intérêt pour le joueur. Ses 8 premiers caractères suffisent pour vérifier que la session est la même d'un lancement à l'autre, sans exposer inutilement l'identifiant.

Étape 7 — Vérifier le comportement​

  1. Lancez npx expo start et ouvrez la version Web (touche w). L'accueil affiche « Joueur anonyme n° » suivi de 8 caractères. Notez-les.
  2. Rechargez la page (F5) : les 8 caractères sont identiques (la session mémorisée est réutilisée).
  3. Dans le tableau de bord Supabase, ouvrez Authentication > Users : un seul utilisateur apparaît, marqué comme anonyme (son identifiant commence par vos 8 caractères).
  4. Dans le navigateur, ouvrez les outils de développement (F12) > Application > Stockage local et supprimez les données du site. Rechargez : un nouveau numéro apparaît et un deuxième utilisateur est créé.

Bonne pratique - Tester les cas limites, pas seulement le cas normal

Recharger la page, effacer les données, couper le réseau : ce sont ces scénarios qui révèlent les bugs de session. Vérifier que la deuxième visite se comporte bien est aussi important que la première.

Étape 8 — Enregistrer votre travail​

git add -A
git status
git commit -m "Connexion anonyme du joueur avec Supabase Auth"
git push -u origin feature/securite

Bonne pratique - -u à la première publication d'une branche

L'option -u relie votre branche locale à la branche du même nom sur GitHub. Les prochains git push de cette branche s'écriront simplement git push.

📌 Une solution

Test de mémorisation/compréhension​


Quel rôle PostgreSQL possède un joueur connecté de façon anonyme ?


Où le client Supabase mémorise-t-il la session du joueur dans notre application ?


Que se passe-t-il si le joueur efface les données de son navigateur (localStorage) ?


Pourquoi partage-t-on une seule promesse (connexionEnCours) entre tous les appels à assurerSessionAnonyme ?


Sur téléphone, pourquoi arrête-t-on le rafraîchissement automatique du jeton quand l'application passe en arrière-plan ?


Une policy RLS écrite « to authenticated » s'applique-t-elle aux joueurs connectés anonymement ?


Que fait l'appel supabase.auth.signInAnonymously() ?



Exercices complémentaires​

Exercice A : observer le jeton​

Dans les outils de développement (F12 > Application > Stockage local), repérez la clé qui contient la session Supabase. Copiez la valeur de access_token sur le site jwt.io et retrouvez dans le contenu le champ role et le champ is_anonymous.

Exercice B : provoquer l'échec​

Désactivez temporairement Allow anonymous sign-ins dans le tableau de bord, supprimez les données du site puis rechargez l'application. Quel message s'affiche à l'écran ? Quel message technique apparaît dans la console ? Réactivez ensuite le réglage et vérifiez que le bouton « Réessayer » fonctionne.


Ce qu'il faut retenir​

NotionRésumé
Connexion anonymeSupabase crée un utilisateur (uuid) sans e-mail ni mot de passe ; réglage à activer dans le tableau de bord.
Rôlesanon = clé seule, authenticated = utilisateur connecté (y compris anonyme).
SessionJeton d'accès (JWT) de courte durée + jeton de rafraîchissement, mémorisés dans le localStorage.
auth.uid()Fonction SQL qui renvoie l'uuid du joueur contenu dans le jeton.
Promesse partagéeconnexionEnCours évite de créer plusieurs utilisateurs pour un seul joueur.
AppStateLe rafraîchissement du jeton ne tourne qu'au premier plan sur téléphone.

Aperçu de la prochaine séance​

Votre joueur est maintenant identifié, mais il reste un simple numéro. Dans la prochaine séance, vous allez lui permettre de choisir un pseudo grâce à un formulaire. Vous créerez la table joueur, écrite avec des droits par colonne et des règles RLS « chacun ne modifie que sa propre fiche », et vous protégerez le formulaire contre le double envoi.