Quand l’authentification Docker bloque la connexion mobile : un bug entre plateformes
Le ticket est arrivé un jeudi après-midi : « Google Sign-In ne fonctionne pas sur mobile. » Le fil d’assistance était court – trois utilisateurs, tous sous iOS, tous signalant le même indicateur de chargement OAuth qui ne menait nulle part. L’application web fonctionnait. La connexion depuis un ordinateur fonctionnait. Seul le mobile échouait, et seulement pour la connexion OAuth. L’authentification par e-mail et mot de passe continuait sans problème.
C’est le genre de rapport de bug qui promet une résolution rapide et se transforme en un après-midi de confusion. Les symptômes apparents – un flux d’authentification défaillant sur une plateforme – suggèrent simultanément une douzaine de pistes. URI de redirection. CORS. Échange de jetons. Stockage de session. Configuration côté client. Chacune est plausible. Aucune ne s’est avérée être le problème.
La pile logicielle
L’application tourne sur une instance Supabase auto-hébergée – la pile Docker Compose complète, déployée sur un serveur dédié. L’architecture de Supabase sépare l’authentification dans son propre microservice : GoTrue, qui gère les flux OAuth, l’émission de jetons et la gestion des utilisateurs. GoTrue communique avec PostgreSQL via une connexion directe à la base de données, lisant et écrivant dans le schéma auth. Le frontend est une application React Native communiquant avec Supabase via la bibliothèque client JavaScript standard.
C’est une configuration relativement courante pour les équipes qui préfèrent contrôler leurs données. Supabase auto-hébergé vous donne la base de données PostgreSQL complète, la couche d’authentification, les abonnements en temps réel et le service de stockage, tout fonctionnant dans des conteneurs que vous gérez. Le compromis – et cet incident en est un exemple précis – est que les services gérés prennent en charge discrètement des détails de configuration qui deviennent des défaillances visibles quand vous les gérez vous-même.
La première hypothèse : inadéquation de l’URI de redirection
Les erreurs OAuth sur mobile commencent presque toujours par les URI de redirection. Les applications natives utilisent des schémas d’URL personnalisés (myapp://auth/callback) plutôt que des URL HTTPS, et une inadéquation entre le retour de redirection enregistré et ce qu’attend le fournisseur OAuth produit exactement le type de défaillance silencieuse que nous observions : le flux OAuth démarre, le navigateur s’ouvre, l’utilisateur s’authentifie, et le retour de redirection n’aboutit jamais.
Nous avons vérifié la configuration de l’URI de redirection dans Google Cloud Console. Vérifié les paramètres du projet Supabase. Vérifié la configuration de liens profonds de l’application React Native. Tout correspondait. Les URI étaient correctement enregistrées des deux côtés. L’écran de consentement OAuth s’est terminé sans erreur – Google acceptait la connexion. La défaillance se produisait après le retour de redirection, quelque part dans l’échange de jetons.
La deuxième hypothèse : CORS
Les en-têtes Cross-Origin Resource Sharing sont le deuxième réflexe pour le débogage OAuth mobile. Quand une application mobile fait des requêtes vers un backend sur un domaine personnalisé, la configuration CORS détermine si le modèle de sécurité du navigateur autorise la requête. Une politique CORS mal configurée produit une classe d’erreurs qui peut sembler identique à une défaillance d’authentification.
Mais en réalité, CORS est appliqué par les navigateurs ; les requêtes qu’une application native envoie via son propre client HTTP n’y sont pas soumises. Et le symptôme – l’authentification par e-mail fonctionnait tandis que OAuth ne fonctionnait pas – était incohérent avec une défaillance CORS. Les deux utilisent le même point de terminaison GoTrue. Si CORS rejetait les requêtes, cela affecterait très probablement les deux, et pas seulement OAuth. Nous avons ajouté une journalisation détaillée au client mobile. Les requêtes atteignaient le service GoTrue. Le service répondait. Les codes de statut étaient 500.
Lire les journaux des conteneurs
Les journaux des conteneurs sont là où le débogage auto-hébergé diverge le plus nettement des services gérés. Avec Supabase Cloud, le tableau de bord expose des journaux structurés de chaque service. Avec Docker auto-hébergé, vous récupérez les journaux des conteneurs individuels directement :
docker logs supabase-auth --since 1h 2>&1 | grep -i error
Le conteneur GoTrue journalisait des erreurs en continu. La ligne pertinente :
level=error msg="Error creating oauth state" error="ERROR: function gen_random_uuid() does not exist (SQLSTATE 42883)"
Ce n’était pas une erreur de redirection OAuth. C’était un échec de résolution de fonction PostgreSQL. Dans les versions de PostgreSQL antérieures à la 13, gen_random_uuid() est fournie uniquement par l’extension pgcrypto – une extension PostgreSQL standard installée par Supabase dans le schéma extensions plutôt que le schéma public par défaut. À partir de PostgreSQL 13, elle est aussi intégrée au cœur du système et peut toujours être résolue ; cette erreur précise indique donc une version majeure antérieure. GoTrue appelait gen_random_uuid() sans qualificateur de schéma, en s’attendant à ce que PostgreSQL la trouve via le chemin de recherche. Il ne la trouvait pas.
Ce que fait search_path
PostgreSQL résout les noms de fonctions et de tables non qualifiés en cherchant une séquence de schémas dans l’ordre. La valeur par défaut est "$user", public : chercher d’abord dans un schéma portant le nom de l’utilisateur de base de données actuel, puis dans public. Si une fonction se trouve dans un autre schéma et n’est pas explicitement qualifiée avec son nom de schéma – extensions.gen_random_uuid() au lieu de gen_random_uuid() – PostgreSQL retourne une erreur « does not exist » même si la fonction existe réellement.
Le déploiement géré de Supabase configure le search_path PostgreSQL pour inclure extensions, auth et public. Cette configuration est documentée dans la base de code de Supabase mais pas mise en évidence dans le guide de configuration auto-hébergé. Sur Supabase géré, elle est appliquée automatiquement. Sur Docker auto-hébergé, elle dépend des variables d’environnement et des scripts d’initialisation appliqués. Notre déploiement ne comportait ni les variables ni les scripts nécessaires.
Pourquoi mobile et pas web
La question à laquelle l’explication des journaux ne répond pas immédiatement : pourquoi l’authentification web fonctionnait-elle tandis que le mobile échouait ? La réponse se trouve dans les différences entre les flux OAuth selon les environnements.
Les applications web utilisant Supabase OAuth utilisent par défaut le flux implicite : le serveur d’autorisation retourne les jetons directement dans le fragment d’URL après le retour de redirection OAuth. Le client les extrait de l’URL, les stocke, et la session est établie sans étape séparée d’échange de jetons – un chemin de code qui n’appelait pas gen_random_uuid().
Les applications mobiles suivant les bonnes pratiques de sécurité actuelles utilisent PKCE (Proof Key for Code Exchange). Avec PKCE, le client génère un code de vérification aléatoire (code verifier), en calcule l’empreinte pour produire un code de défi (code challenge), et envoie le défi au serveur d’autorisation. Après le retour de redirection, le client envoie le code de vérification original au point de terminaison de délivrance des jetons, qui le vérifie avant d’émettre les jetons. Ce flux PKCE était le chemin de code qui échouait. GoTrue appelait gen_random_uuid() lors de la génération de l’état OAuth pour le flux PKCE, et le schéma extensions n’était pas dans le chemin de recherche.
Le flux implicite sur web contournait ce chemin de code spécifique. PKCE sur mobile non. Une différence subtile dans l’implémentation OAuth a exposé une erreur de configuration qui existait dans la pile logicielle Docker depuis le déploiement – elle n’avait simplement pas été sollicitée jusqu’à ce qu’un client mobile tente un flux PKCE.
Le correctif
Le correctif approprié est de s’assurer que le search_path PostgreSQL inclut tous les schémas dont GoTrue et PostgREST dépendent. Dans une configuration Docker Compose, il est préférable de l’appliquer au niveau de la base de données pour qu’il persiste quel que soit le conteneur qui se connecte :
-- Exécuter une fois contre la base de données
ALTER DATABASE postgres SET search_path TO extensions, auth, public, storage;
ALTER ROLE authenticator SET search_path TO extensions, auth, public, storage;
ALTER ROLE supabase_auth_admin SET search_path TO extensions, auth, public, storage;
Une alternative est de passer le paramètre via la configuration du service PostgreSQL dans Docker Compose :
services:
db:
image: supabase/postgres:15.1.0.117
command:
- postgres
- -c
- search_path=extensions,auth,public,storage
Nous avons appliqué les deux. Après le redémarrage du conteneur GoTrue, le flux OAuth mobile s’est terminé sans erreur. Apple Sign-In – que nous avions hésité à tester pendant le processus de débogage – a également fonctionné immédiatement, confirmant que le problème était spécifique au flux et non au fournisseur.
Ce que cet incident révèle sur le débogage auto-hébergé
Le chemin de débogage – de « authentification défaillante sur mobile » à « mauvais search_path dans PostgreSQL » – a pris plus de temps qu’il n’aurait dû. Plusieurs leçons méritent d’être tirées.
Les journaux des conteneurs font foi. Les deux hypothèses incorrectes ont été poursuivies avant de lire les journaux GoTrue. Les journaux contenaient l’erreur exacte dès la première entrée pertinente. Dans une pile Docker auto-hébergée, lire les journaux est la première étape, pas un dernier recours après avoir épuisé les autres théories.
Les défaillances spécifiques à une plateforme dans un backend partagé indiquent presque toujours une différence dans le chemin de code client, pas une différence dans la façon dont le backend traite ce client spécifique. Le backend ne sait pas qu’il sert une application mobile plutôt qu’un navigateur. Il suit le chemin de code déterminé par les paramètres de la requête. Comprendre quel chemin de code PKCE active – par opposition au flux implicite – aurait pointé immédiatement vers l’état du flux PKCE.
Les services gérés et les services auto-hébergés ne sont pas des configurations équivalentes. Supabase Cloud applique des détails de configuration qui sont documentés mais pas automatiquement appliqués dans la configuration Docker Compose. Quand quelque chose fonctionne sur Supabase Cloud mais échoue en auto-hébergé, l’écart est presque toujours dans l’une de ces configurations. Le système de suivi des problèmes du dépôt officiel auto-hébergé est souvent le moyen le plus rapide de localiser le paramètre manquant.
Un bug qui « n’affecte qu’une plateforme » est souvent un bug qui a toujours été présent, exposé seulement par le premier client à exercer un chemin de code spécifique. Dans ce cas, l’erreur de search_path existait depuis le jour où la pile Docker a été déployée. Il a fallu des mois et une implémentation OAuth mobile pour la rendre visible.