Aller au contenu

Mobile Auth Session Contract For Front

Ce document formalise le contrat cible entre l'app mobile et le backend pour la nouvelle auth first-party.

Statut: contrat cible V1, pas encore implémentation courante Date de mise à jour: 2026-04-28

Référence backend longue: heymoov-api/reference/auth_target_architecture.md Référence onboarding transverse: heymoov-docs/reference/contracts/reference-auth-first-party-onboarding.md

Décisions V1

  • passkeys = méthode de login nominale
  • password = fallback optionnel de migration, recovery et support
  • magic link login = hors cible
  • Hydra navigateur ne fait pas partie du login mobile first-party cible
  • pas de JWT mobile comme source de vérité d'auth ou de rôles
  • session mobile = accessToken opaque court + refreshToken opaque rotatif
  • sessionId et deviceId sont des identifiants backend non secrets
  • le contexte UI vient du bootstrap courant GET /v1/mobile/api/me
  • pas de device-bound refresh en V1

SessionEnvelope

Tous les flows d'auth réussis renvoient le même envelope.

{
  "accessToken": "opaque_random_token",
  "accessExpiresAt": "2026-04-27T12:15:00Z",
  "refreshToken": "opaque_random_token",
  "refreshExpiresAt": "2026-05-27T12:00:00Z",
  "sessionId": "mobile_session_id",
  "deviceId": "40a86471-a4b1-4105-bf63-5758db93f47b",
  "bootstrapVersion": 42
}

Règles client:

  • envoyer Authorization: Bearer <accessToken> sur les routes privées mobile
  • ne jamais décoder accessToken
  • ne jamais déduire userId, personId, roles ou permissions depuis un token local
  • stocker refreshToken, sessionId, deviceId et bootstrapVersion en secure storage
  • deviceId est le mobile_devices.id émis par le backend dans le SessionEnvelope
  • le mobile ne doit jamais générer deviceId lui-même, ni y mettre un identifiant d'installation local
  • garder accessToken en mémoire de préférence, ou le stocker en secure storage seulement si le client doit le persister
  • remplacer l'envelope local complet après chaque refresh réussi
  • supprimer tout l'état local auth après logout ou erreur de refresh terminale

Login et deviceId

Sur les endpoints qui ouvrent une session mobile (password/login, passkeys/login/verify, onboarding ou recovery):

  • au premier login, ou si aucun deviceId backend n'est disponible, omettre deviceId
  • si un deviceId est envoyé, il doit être la valeur retournée précédemment dans un SessionEnvelope
  • un deviceId envoyé doit être un UUID backend connu comme mobile_devices.id
  • deviceId ne participe pas à la vérification passkey/WebAuthn
  • les métadonnées appareil (platform, appVersion, deviceName, deviceModel, osVersion) restent les champs à envoyer pour décrire le device courant

Endpoints Mobile

Passkeys:

  • POST /v1/mobile/auth/passkeys/login/options
  • POST /v1/mobile/auth/passkeys/login/verify
  • POST /v1/mobile/auth/passkeys/register/options
  • POST /v1/mobile/auth/passkeys/register/verify

Fallbacks:

  • POST /v1/mobile/auth/password/login

Onboarding et recovery:

  • POST /v1/public/onboarding/resolve
  • POST /v1/public/onboarding/passkey/options
  • POST /v1/public/onboarding/passkey/verify
  • POST /v1/public/onboarding/password/complete
  • POST /v1/public/account-recovery/request
  • POST /v1/public/account-recovery/resolve
  • POST /v1/public/account-recovery/passkey/options
  • POST /v1/public/account-recovery/passkey/verify
  • POST /v1/public/account-recovery/password/complete

Session:

  • POST /v1/mobile/session/refresh
  • POST /v1/mobile/session/logout
  • POST /v1/mobile/session/logout-all
  • GET /v1/mobile/session/devices
  • DELETE /v1/mobile/session/devices/{deviceId}

Les endpoints d'auth, de refresh et de bootstrap courant (/v1/mobile/api/me) doivent renvoyer Cache-Control: no-store.

Bootstrap

Request:

GET /v1/mobile/api/me
Authorization: Bearer <accessToken>

Response cible:

{
  "user": {},
  "person": {},
  "roles": [],
  "permissions": [],
  "auth": {
    "sessionId": "mobile_session_id",
    "deviceId": "40a86471-a4b1-4105-bf63-5758db93f47b",
    "authMethod": "passkey",
    "authLevel": "aal2",
    "passkeyEnrolled": true,
    "reauthRequiredAt": "2026-04-27T14:00:00Z"
  },
  "bootstrapVersion": 42
}

Le bootstrap doit être rechargé:

  • au démarrage à froid si une session existe
  • après login réussi
  • après refresh réussi si bootstrapVersion change
  • après retour au premier plan au-delà d'un seuil défini
  • après réception d'un événement d'invalidation de contexte

Le bootstrap ne doit pas être rechargé à chaque navigation ou à chaque requête métier.

Refresh

Request:

{
  "refreshToken": "opaque_random_token"
}

Response:

  • SessionEnvelope

Règles client:

  • appeler refresh quand l'API privée renvoie une expiration d'access token ou avant expiration connue
  • ne pas envoyer deviceId dans la payload de refresh; le refreshToken identifie déjà la session serveur
  • remplacer immédiatement l'ancien refreshToken par le nouveau
  • ne jamais réutiliser volontairement un ancien refreshToken
  • si refresh renvoie une erreur terminale, supprimer l'état local et retourner au login

Erreurs Contractuelles

Les codes exacts peuvent être ajustés, mais le client doit pouvoir distinguer:

  • invalid_credentials
  • challenge_expired
  • session_expired
  • refresh_reused
  • profile_banned
  • reauth_required

Suivi Mobile

  • TODO: remplacer le flow navigateur Hydra par les écrans first-party
  • TODO: intégrer passkeys iOS/Android
  • TODO: ajouter password login fallback
  • TODO: ajouter onboarding passkey/password
  • TODO: ajouter recovery passkey/password sans login direct par lien
  • TODO: stocker et remplacer le SessionEnvelope selon la politique secure storage/mémoire
  • TODO: ajouter refresh automatique avec sérialisation des appels concurrents
  • TODO: appeler le bootstrap /v1/mobile/api/me au démarrage, après login et après changement de version
  • TODO: supprimer la lecture du UI JWT pour le routing et les rôles
  • TODO: ajouter logout appareil et logout global
  • TODO: gérer les erreurs terminales de refresh par retour login
  • TODO: migrer ou purger l'ancien état SecureStore token/sid