Le mythe du token éternel face à la réalité des terminaux
Le mobile n'est pas un navigateur web. Je répète cette phrase constamment. Vous ne disposez d'aucun mécanisme natif équivalent aux cookies HttpOnly pour protéger vos jetons d'accès. La majorité des développeurs traite pourtant une application iOS ou Android comme un simple client web déporté. C'est une erreur architecturale fatale. Vous récupérez un JSON Web Token depuis votre fournisseur d'identité. Vous le jetez vulgairement dans les SharedPreferences. Vous espérez que personne ne viendra fouiller dans le système de fichiers.
Je me demande souvent si l'industrie ne fait pas fausse route avec certaines spécifications. L'intégration d'un Single Sign-On sur mobile introduit une complexité d'état terrifiante. Le composant webview classique est mort. Google l'a tué avec raison. La documentation officielle de l'IETF via la RFC 8252 interdit formellement l'usage des webviews intégrées pour les flux OAuth2. Vous devez utiliser des onglets personnalisés comme Chrome Custom Tabs ou SFSafariViewController. Ces composants partagent l'état de session avec le navigateur système. Cela permet d'obtenir ce fameux SSO fluide.
L'utilisateur se connecte une fois. Il accède ensuite à tout votre écosystème. Franchement cette fluidité apparente masque un cauchemar de synchronisation. Les sessions sont invalidé par le serveur central sans que l'application mobile n'en soit informée. L'utilisateur se retrouve avec un jeton obsolète. L'application crashe ou affiche des écrans blancs. La gestion de cet état asynchrone requiert une architecture blindée. Vous pouvez consulter notre vision de ces architectures sur la page d'accueil de notre site.
PKCE ou la mort prématurée de votre architecture d'authentification
Le flux implicite d'OAuth2 est une aberration sécuritaire. Ne l'utilisez jamais. Les spécifications actuelles exigent le flux Authorization Code couplé à PKCE. L'acronyme signifie Proof Key for Code Exchange. Ce mécanisme empêche l'interception du code d'autorisation par une application malveillante. Le concept est brutalement efficace. Le client génère une chaîne aléatoire à haute entropie. Il calcule l'empreinte SHA-256 de cette chaîne. Il envoie cette empreinte lors de la requête initiale.
L'interception des redirections URI est un sport national sur Android. N'importe quelle application peut déclarer écouter le même schéma d'URI que le vôtre. Le système d'exploitation demandera parfois à l'utilisateur de choisir l'application cible. L'attaquant récupère le code d'autorisation , on assiste alors à un vol de session immédiat. PKCE bloque cette attaque. Le serveur d'autorisation exige la chaîne aléatoire originale pour délivrer le jeton final. L'attaquant ne possède pas cette chaîne secrète.
Ce dévelopement nécessite une maîtrise parfaite des appels cryptographiques natifs. Les bibliothèques open source comme AppAuth fournissent une base solide. Google maintient activement ce projet. Je doute parfois de la pertinence de tout coder soi-même quand des fondations certifiées existent. Le flux PKCE garantit une sécurité absolue contre l'interception locale. Bien sûr cette sécurité cryptographique s'effondre totalement si le terminal est rooté ou jailbreaké. Un attaquant avec des privilèges superutilisateur lira directement la mémoire de votre processus.
Anatomie clinique d'un vol de session sur iOS et Android
Vous pensez maîtriser votre surface d'attaque. Vous avez tort. Un smartphone est un environnement hostile par nature. L'application cohabite avec des dizaines d'autres processus potentiellement malveillants. La majorité des faille provient d'une mauvaise compréhension des mécanismes d'isolation du système d'exploitation.
Voici les vecteurs de compromission que j'observe quotidiennement lors de mes audits :
- Le détournement des schémas d'URI personnalisés lors de la redirection OAuth2.
- L'injection de code JavaScript dans des webviews obsolètes ou mal configurées.
- L'exploitation des Activities Android exportées sans permissions strictes.
- L'extraction des jetons stockés en clair via un accès physique au terminal .La récupération est triviale avec des outils forensiques basiques.
- L'interception des flux réseau via un proxy Man-in-the-Middle en l'absence de Certificate Pinning.
- La fuite de jetons d'accès dans les journaux système via des instructions de débogage oubliées en production.
- L'utilisation de filtres d'intention trop permissifs permettant à des tiers de forcer l'ouverture de sessions arbitraires.
Ces failles ne sont pas théoriques. Des entreprises massives ont subi des brèches dévastatrices à cause de ces négligences. Le vol d'un Refresh Token est la pire catastrophe possible. Ce jeton a une durée de vie longue. Il permet de générer des accès valides à l'infini. Il faut adopter une approche radicale pour verrouiller ces vecteurs. Notre méthodologie de conception intègre cette paranoïa dès les premières phases du projet.
Stockage cryptographique et paranoïa justifiée
Stocker un secret sur un téléphone relève du défi physique. Vous ne pouvez pas faire confiance au système de fichiers classique. Vous devez exploiter les modules matériels dédiés. Sur Android on parle du Keystore. Sur iOS on parle du Secure Enclave. Ces puces isolent les opérations cryptographiques du reste du système .Il est impossible d'en extraire la clé privée. Même le système d'exploitation n'y a pas accès directement.
L'approche correcte consiste à chiffrer vos jetons OAuth2 avant de les persister. Vous générez une paire de clés asymétriques adossée au matériel. Vous utilisez la clé publique pour chiffrer le Refresh Token. Vous stockez le résultat chiffré dans les SharedPreferences ou le Keychain. Lors du redémarrage de l'application vous demandez au module matériel de déchiffrer la donnée.
Il faut aller plus loin. L'accès à cette clé privée matérielle doit être conditionné par une authentification biométrique. L'utilisateur pose son doigt sur le capteur. Le module matériel déverrouille l'usage de la clé. La donnée est déchiffrée. L'application obtient son jeton d'accès frais.
Je recommande généralement deux stratégies d'implémentation :
- Le chiffrement asymétrique strict avec invalidation automatique de la clé lors de l'ajout d'une nouvelle empreinte biométrique au niveau de l'OS.
- La liaison cryptographique du jeton d'accès via le standard DPoP pour prouver la possession de la clé matérielle lors de chaque appel API.
L'invalidation automatique est cruciale. Si un attaquant connaît le code PIN du téléphone et ajoute son propre visage à FaceID. Il ne pourra pas utiliser votre application. La clé matérielle précédente sera détruite par le système. Des acteurs comme Okta proposent des SDK mobiles qui gèrent cette complexité. Slack utilise également des mécanismes similaires pour ses déploiements Enterprise Grid.
Je refuse catégoriquement de valider une architecture qui ignore ces contraintes matérielles. L'utilisation du Secure Enclave est une nécessité non négociable.
La révocation des accès en environnement asynchrone
Gérer la fin de vie d'une session mobile provoque souvent des sueurs froides. La déconnexion locale ne suffit pas. Supprimer le jeton de la mémoire du téléphone n'invalide pas la session sur le serveur d'autorisation. Un attaquant ayant copié le jeton continuera d'accéder à vos API. Vous devez impérativement implémenter le point de terminaison de révocation défini par la RFC 7009.
Le client mobile doit envoyer une requête HTTP explicite pour détruire le Refresh Token côté serveur. Sauf si l'utilisateur décide de révoquer l'accès depuis un autre appareil en plein milieu du flux... L'état devient alors totalement imprévisible. Le mobile tente de rafraîchir son accès. Le serveur répond par une erreur HTTP 400. L'application doit intercepter ce code spécifique. Elle doit purger son stockage local. Elle doit renvoyer l'utilisateur vers l'écran de connexion initial.
La rotation des Refresh Tokens ajoute une couche de protection redoutable. Chaque fois que l'application demande un nouveau jeton d'accès court. Le serveur renvoie également un nouveau Refresh Token. L'ancien est immédiatement invalidé. Si un attaquant utilise un vieux Refresh Token volé. Le serveur détecte la compromission. Il révoque instantanément toute la chaîne de confiance associée à cet utilisateur. C'est une mécanique implacable.
Je remarque cependant une fragilité inhérente à cette rotation sur mobile. Les coupures réseau perturbent ce ballet cryptographique. L'application envoie sa demande de rafraîchissement. Le serveur invalide l'ancien jeton. Il génère le nouveau. Il l'envoie. La connexion saute. L'application ne reçoit jamais le nouveau jeton. Le serveur considère l'ancien comme mort. L'utilisateur est violemment déconnecté. Le traitement de ces conditions de course réseau requiert des fenêtres de grâce côté serveur.
Cette complexité rebute beaucoup d'équipes techniques. Vous trouverez des exemples de résolutions architecturales complexes parmi nos références. La persistance d'un état d'authentification valide sur mobile est une guerre d'usure contre l'instabilité du réseau .Une guerre que vous devez mener avec des armes cryptographiques modernes.