Authentication
Flow d'authentification mobile complet : login, tokens JWT, biométrie et gestion des sessions multi-devices.
Overview
L'authentification mobile YaniPay implémente un système multi-couches combinant credentials classiques, authentification biométrique et gestion sécurisée des tokens.
JWT Tokens
Access token (15min) + Refresh token (7 jours)
Biometric
Face ID / Touch ID pour actions sensibles
Device Sessions
Max 5 devices simultanés par compte
Pourquoi l'authentification multi-couches ?
Dans le contexte fintech de YaniPay, la sécurité de l'authentification est primordiale. Un simple mot de passe ne suffit pas pour protéger l'accès à des comptes bancaires et des transactions financières. Notre approche multi-couches répond à plusieurs exigences :
Conformité PSD2/DSP2
La directive européenne sur les services de paiement impose l'authentification forte (SCA) pour les transactions sensibles. Notre système combine "ce que l'utilisateur sait" (password) avec "ce qu'il est" (biométrie).
Protection contre le vol
Même si un attaquant obtient les credentials, il ne peut pas accéder au compte sans le device physique enregistré et l'empreinte biométrique de l'utilisateur.
Expérience utilisateur
L'authentification biométrique permet un accès rapide et fluide tout en maintenant un niveau de sécurité élevé. L'utilisateur n'a pas à retenir de codes complexes.
Auditabilité
Chaque connexion est tracée avec l'identifiant du device, la localisation approximative et l'heure. En cas de fraude, nous pouvons retracer l'historique complet.
Conformité RGPD
Cas d'utilisation
Découvrez comment l'authentification YaniPay s'adapte aux différents contextes d'utilisation :
Emma
Jeune active
Login rapide avec Face ID
Emma ouvre l'app YaniPay chaque matin pour vérifier son solde. Grâce à Face ID, elle accède à son compte en moins de 2 secondes sans avoir à taper son mot de passe. Pour les paiements importants, Face ID se déclenche automatiquement.
Pierre
Père de famille
Sécurité multi-devices
Pierre utilise YaniPay sur son iPhone personnel et la tablette familiale. Quand il se connecte sur un nouvel appareil, il reçoit une notification push sur son téléphone principal pour valider la connexion. Il peut voir et révoquer les sessions actives à tout moment.
Sarah
Voyageuse
Session expirée en voyage
En vacances à l'étranger, Sarah n'a pas utilisé l'app depuis 8 jours. Son refresh token a expiré mais elle garde accès aux données en cache offline. Dès qu'elle se reconnecte avec son mot de passe, toutes ses transactions se synchronisent automatiquement.
Marc
Commerçant
Protection contre le vol
Le téléphone de Marc est volé. Il se connecte sur yanipay.com et révoque immédiatement toutes les sessions actives. Le voleur ne peut plus accéder au compte même avec le téléphone déverrouillé car l'authentification biométrique est liée au compte, pas au device.
Flow d'authentification
Vue d'ensemble du processus d'authentification depuis l'ouverture de l'app jusqu'à l'accès aux données :
Authentification silencieuse
Login Flow
Séquence complète d'authentification utilisateur.
1. Login Initial
// Request
{
"email": "user@example.com",
"password": "securePassword123",
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"deviceInfo": {
"platform": "ios",
"model": "iPhone 15 Pro",
"osVersion": "17.2",
"appVersion": "1.0.0"
}
}
// Response (200 OK)
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 900, // 15 minutes
"user": {
"id": "usr_123456",
"email": "user@example.com",
"firstName": "Jean",
"lastName": "Dupont",
"kycLevel": 2,
"roles": ["user"]
}
}2. Service Implementation
import { apiClient } from './api';
import * as SecureStore from 'expo-secure-store';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
interface LoginRequest {
email: string;
password: string;
deviceId: string;
deviceInfo: DeviceInfo;
}
interface AuthResponse {
accessToken: string;
refreshToken: string;
expiresIn: number;
user: User;
}
export const authService = {
async login(email: string, password: string): Promise<AuthResponse> {
// Récupère ou génère l'ID unique du device
let deviceId = await SecureStore.getItemAsync('deviceId');
if (!deviceId) {
deviceId = crypto.randomUUID();
await SecureStore.setItemAsync('deviceId', deviceId);
}
const deviceInfo = {
platform: Device.osName?.toLowerCase() || 'unknown',
model: Device.modelName || 'unknown',
osVersion: Device.osVersion || 'unknown',
appVersion: Constants.expoConfig?.version || '1.0.0',
};
const response = await apiClient.post<AuthResponse>('/auth/login', {
email,
password,
deviceId,
deviceInfo,
});
// Stocke les tokens de manière sécurisée
await SecureStore.setItemAsync('accessToken', response.data.accessToken);
await SecureStore.setItemAsync('refreshToken', response.data.refreshToken);
// Stocke l'expiration pour le refresh proactif
const expiresAt = Date.now() + response.data.expiresIn * 1000;
await SecureStore.setItemAsync('tokenExpiresAt', expiresAt.toString());
return response.data;
},
};Token Management
Gestion des tokens JWT avec refresh automatique.
Token Lifecycle
- • Access Token: 15 minutes (900 secondes) - Court pour limiter l'impact en cas de vol
- • Refresh Token: 7 jours (604800 secondes) - Permet de rester connecté
- • Refresh proactif: 5 minutes avant expiration - Évite les erreurs 401
- • Max sessions: 5 devices simultanés par compte
Sécurité des tokens
expo-secure-store qui utilise le Keychain (iOS) ou Keystore (Android) pour un chiffrement matériel.Token Refresh
// Request
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
// Response (200 OK)
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": 900
}
// Response (401 Unauthorized) - Token expiré ou invalide
{
"error": "REFRESH_TOKEN_EXPIRED",
"message": "Session expired. Please login again."
}Proactive Refresh Strategy
// Dans le store Zustand (authStore.ts)
import { create } from 'zustand';
interface AuthState {
isAuthenticated: boolean;
user: User | null;
sessionExpiresAt: number | null;
refreshSession: () => Promise<void>;
}
export const useAuthStore = create<AuthState>((set, get) => ({
isAuthenticated: false,
user: null,
sessionExpiresAt: null,
refreshSession: async () => {
try {
const refreshToken = await SecureStore.getItemAsync('refreshToken');
if (!refreshToken) throw new Error('No refresh token');
const response = await apiClient.post('/auth/refresh', { refreshToken });
await SecureStore.setItemAsync('accessToken', response.data.accessToken);
const expiresAt = Date.now() + response.data.expiresIn * 1000;
await SecureStore.setItemAsync('tokenExpiresAt', expiresAt.toString());
set({ sessionExpiresAt: expiresAt });
} catch (error) {
// Refresh failed - clear session
await get().logout();
throw error;
}
},
}));
// Hook pour refresh proactif
export function useTokenRefresh() {
const { sessionExpiresAt, refreshSession } = useAuthStore();
useEffect(() => {
if (!sessionExpiresAt) return;
// Refresh 5 minutes avant expiration
const refreshTime = sessionExpiresAt - 5 * 60 * 1000;
const delay = refreshTime - Date.now();
if (delay > 0) {
const timeout = setTimeout(refreshSession, delay);
return () => clearTimeout(timeout);
}
}, [sessionExpiresAt, refreshSession]);
}Biometric Authentication
Authentification Face ID / Touch ID pour les actions sensibles.
Setup Biometric
// Request - Enregistre la biométrie pour ce device
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"biometricType": "face_id", // ou "touch_id", "fingerprint"
"biometricToken": "signed_device_attestation..."
}
// Response (200 OK)
{
"success": true,
"biometricEnabled": true,
"message": "Biometric authentication enabled for this device"
}Implementation
import * as LocalAuthentication from 'expo-local-authentication';
export const biometricService = {
// Vérifie si le device supporte la biométrie
async isAvailable(): Promise<boolean> {
const compatible = await LocalAuthentication.hasHardwareAsync();
if (!compatible) return false;
const enrolled = await LocalAuthentication.isEnrolledAsync();
return enrolled;
},
// Récupère le type de biométrie disponible
async getBiometricType(): Promise<'face_id' | 'touch_id' | 'fingerprint' | null> {
const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
if (types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION)) {
return 'face_id';
}
if (types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT)) {
return Platform.OS === 'ios' ? 'touch_id' : 'fingerprint';
}
return null;
},
// Authentification biométrique
async authenticate(reason: string = 'Confirmer votre identité'): Promise<boolean> {
try {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: reason,
cancelLabel: 'Annuler',
disableDeviceFallback: false, // Permet PIN/password en fallback
});
return result.success;
} catch (error) {
console.error('Biometric auth failed:', error);
return false;
}
},
// Active la biométrie pour le compte
async enableBiometric(): Promise<void> {
const deviceId = await SecureStore.getItemAsync('deviceId');
const biometricType = await this.getBiometricType();
if (!biometricType) {
throw new Error('Biometric not available on this device');
}
// Génère un token d'attestation device
const biometricToken = await this.generateDeviceAttestation();
await apiClient.post('/auth/biometric', {
deviceId,
biometricType,
biometricToken,
});
await SecureStore.setItemAsync('biometricEnabled', 'true');
},
};Actions Nécessitant Biométrie
- • Afficher les détails de carte (CVV, numéro complet)
- • Envoyer de l'argent (montant >100€)
- • Modifier les paramètres de sécurité
- • Exporter les données du compte
- • Ajouter un nouveau bénéficiaire
Device Sessions
Gestion des sessions multi-devices.
// Response (200 OK)
{
"devices": [
{
"id": "dev_123",
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"name": "iPhone 15 Pro",
"platform": "ios",
"lastActive": "2026-02-08T14:30:00Z",
"biometricEnabled": true,
"isCurrent": true
},
{
"id": "dev_456",
"deviceId": "660f9511-f30c-52e5-b827-557766551111",
"name": "iPad Pro",
"platform": "ios",
"lastActive": "2026-02-07T10:15:00Z",
"biometricEnabled": false,
"isCurrent": false
}
],
"maxDevices": 5,
"currentCount": 2
}Revoke Device Session
// Révoque la session d'un device spécifique
// Endpoint: DELETE /api/v1/mobile/user/devices/dev_456
// Response (200 OK)
{
"success": true,
"message": "Device session revoked"
}
// Note: Le device révoqué sera déconnecté au prochain appel APILogout
Déconnexion sécurisée avec nettoyage des données.
export const authService = {
async logout(): Promise<void> {
try {
// Notifie le backend pour invalider les tokens
const deviceId = await SecureStore.getItemAsync('deviceId');
await apiClient.post('/auth/logout', { deviceId });
} catch (error) {
// Continue même si l'appel API échoue
console.warn('Logout API call failed:', error);
} finally {
// Nettoie TOUJOURS le stockage local
await this.clearLocalSession();
}
},
async clearLocalSession(): Promise<void> {
// Supprime tous les tokens et données sensibles
const keysToRemove = [
'accessToken',
'refreshToken',
'tokenExpiresAt',
'biometricEnabled',
'userProfile',
'walletCache',
];
await Promise.all(
keysToRemove.map(key => SecureStore.deleteItemAsync(key))
);
// Reset du store Zustand
useAuthStore.getState().reset();
// Nettoie le cache React Query
queryClient.clear();
},
// Logout de tous les devices
async logoutAllDevices(): Promise<void> {
await apiClient.post('/auth/logout-all');
await this.clearLocalSession();
},
};Auto-Logout Conditions
- • Inactivité de 5 minutes (app en foreground)
- • App mise en background pendant plus de 15 minutes
- • Refresh token expiré (7 jours sans activité)
- • Device révoqué depuis un autre appareil
- • Tentatives d'accès frauduleuses détectées
Related Documentation
Références & Resources
Documentation et ressources pour approfondir l'authentification mobile :
🔐 Authentification & Sécurité
- Expo LocalAuthentication — API officielle pour Face ID et Touch ID
- Expo SecureStore — Stockage chiffré pour les tokens
- JWT Introduction — Comprendre les JSON Web Tokens
- Auth0 Refresh Tokens Guide — Best practices pour le refresh token
📋 Conformité & Réglementation
- Directive PSD2 (DSP2) — Texte officiel sur l'authentification forte
- EBA RTS on SCA — Standards techniques pour l'authentification forte
- OWASP Mobile Top 10 — Risques de sécurité mobile à éviter
💻 Implémentation & Code
- Zustand Documentation — State management pour l'auth store
- Axios Interceptors — Injection automatique des tokens
- NextAuth.js — Backend auth utilisé par YaniPay
Points de vigilance sécurité
- • Ne jamais logger les tokens en production
- • Toujours valider les tokens côté serveur, jamais seulement côté client
- • Implémenter le rate limiting sur les endpoints d'authentification
- • Notifier l'utilisateur par email/push lors d'une nouvelle connexion