feat(auth): jeton Bearer en repli du cookie de session (portage mobile #39, phase 1) #54
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/mobile-bearer-auth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Première phase du portage mobile #39. Purement additive côté navigateur : le cookie
sessionhttpOnly reste le mécanisme principal et rien ne change pour le web existant. Mergeable seule, avant même d'introduire Capacitor.Pourquoi
Front et API sont servis depuis la même origine en prod, d'où le cookie
SameSite=Laxqui suffit aujourd'hui. Une WebView Capacitor fait tourner le bundle souscapacitor://localhost/https://localhost— un site différent (SameSite est basé sur le domaine) de l'API, quel que soit l'hébergement : le cookie ne part jamais. Sans repli, l'auth casserait silencieusement dans l'appli native.Contenu
Backend
packages/shared: nouveau typeAuthResponse=SafeUserProfile+token. Documenté : le JWT est aussi renvoyé dans le corps, identique à la valeur du cookie, pour les clients dont l'origine ne peut pas recevoir ce cookie.auth.routes.ts:POST /auth/loginetPOST /auth/signuprenvoient{ ...profile, token }(leres.cookie(...)est inchangé). Additif : tous les champs de profil existants restent à leur place.require-auth.ts: nouvel helperreadBearerToken(); lecture du cookie en priorité, repli surAuthorization: Bearer <jwt>quand le cookie est absent. Le chemin cookie est inchangé au octet près — quand un cookie est présent, le ternaire le renvoie sans jamais toucher l'en-tête.Frontend (
apps/web)api/token-store.ts(nouveau) : interfaceTokenStore(get/set/clear) +noopTokenStore, l'implémentation navigateur — un vrai no-op, le cookie fait tout.api/client.ts:activeTokenStore(module-level, défaut = no-op) + exportsetTokenStore(), le point d'injection qu'utilisera la phase 2 (@capacitor/preferencesquandCapacitor.isNativePlatform())._requestajoute l'en-têteAuthorizationuniquement si un jeton existe ;login/signuple stockent,logout/deleteAccountl'effacent.AuthContext.tsxnon modifié — délibéré. Le cycle de vie du jeton est entièrement encapsulé dans le client (mieux que le découpage suggéré par le ticket : aucun risque qu'un futur appelant deapiClient.loginoublie de stocker le jeton, et le jeton ne transite jamais par l'état React).Hors de cette PR (phases suivantes de #39)
@capacitor/preferencesdeTokenStore+ son branchement viasetTokenStore()— arrive avec les dépendances Capacitor en phase 2.Tests
apps/api/test/auth.test.ts, +5 cas) :login/signupexposent le jeton dans le corps (et il est bien égal à la valeur du cookie) ;requireAuthauthentifie une route protégée viaAuthorization: Bearersans cookie ; rejette un jeton Bearer malformé (401NOT_AUTHENTICATED) ; ignore un schéma non-Bearer.pnpm build+pnpm lintverts localement. Pas de base Postgres locale sur ce poste → les tests Mocha DB s'exécutent en CI.auth.feature; le mécanisme reste 100 % no-op hors natif (un test Cypress dédié n'apporterait rien, cf. ticket).Avancement de #39 — l'issue reste ouverte (phases 2 et 3 à suivre).
🤖 Generated with Claude Code