Chapitre 1319 minIntermédiaire
Sécuriser une API REST et une application mobile
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- classer une faille d'API dans l'OWASP API Security Top 10 ;
- filtrer les objets et les propriétés renvoyés par une API ;
- limiter les ressources qu'une API consomme ;
- stocker un secret sur un téléphone sans l'exposer ;
- expliquer les précautions de la biométrie sur mobile.
4 exercices corrigés1 schéma
Une API REST expose directement les données et les fonctions d'une application à d'autres programmes : front web, application mobile, services partenaires. Sa sécurité repose sur l'authentification de chaque appel, le contrôle d'accès à chaque objet et à chaque propriété, et la limitation des ressources consommées. Ce chapitre suit l'OWASP API Security Top 10 (édition 2023), puis présente les risques propres aux applications mobiles (OWASP Mobile Top 10, édition 2024) : stockage sur le téléphone, communication, authentification biométrique.
OWASP API Security Top 10 (2023)
Les API ont leurs propres failles dominantes, d'où une liste dédiée de l'OWASP.
| Code | Risque | En une phrase | Exemple dans l'API d'AppliFrais |
|---|---|---|---|
| API1:2023 | Broken Object Level Authorization (BOLA) | Pas de vérification du droit sur l'objet demandé | GET /api/fiches/1188 renvoie la fiche d'un autre visiteur |
| API2:2023 | Broken Authentication | Authentification contournable ou jetons mal gérés | Jeton JWT sans expiration, signature non vérifiée |
| API3:2023 | Broken Object Property Level Authorization | Pas de contrôle propriété par propriété, en lecture comme en écriture | La réponse contient mdp_hash ; le visiteur peut modifier role |
| API4:2023 | Unrestricted Resource Consumption | Aucune limite de débit, de taille ou de volume | GET /api/fiches?limite=1000000 |
| API5:2023 | Broken Function Level Authorization | Fonctions d'administration accessibles à tous | Un visiteur appelle DELETE /api/utilisateurs/12 |
| API6:2023 | Unrestricted Access to Sensitive Business Flows | Un processus métier automatisable à l'excès | Un script crée des centaines de demandes d'avance |
| API7:2023 | Server Side Request Forgery | L'API télécharge une URL fournie sans la contrôler | Import d'un justificatif par URL (chapitre 3) |
| API8:2023 | Security Misconfiguration | Réglages dangereux | CORS ouvert à tous, traces d'erreur renvoyées au client |
| API9:2023 | Improper Inventory Management | Versions et routes oubliées | L'ancienne /api/v1/, sans contrôle d'accès, tourne encore |
| API10:2023 | Unsafe Consumption of APIs | Confiance aveugle dans une API tierce | Les données d'un service partenaire sont insérées en base sans validation |
Authentifier chaque appel
Une API n'a pas de « page de connexion » qui protège le reste : chaque requête doit prouver son identité, et chaque route doit être protégée par défaut (refuser par défaut).
| Mécanisme | Usage | Précautions |
|---|---|---|
Jeton JWT ou jeton opaque dans Authorization: Bearer … | Un utilisateur, via un front ou une application mobile | Expiration courte, signature et algorithme vérifiés (chapitre 11) |
Clé d'API dans un en-tête (X-API-Key) | Un programme partenaire, de serveur à serveur | Une clé par partenaire, droits limités, révocable, stockée hachée côté serveur, jamais dans une application mobile ou un front |
| OAuth 2.0 (jeton d'accès avec scopes) | Accès délégué au nom d'un utilisateur, par une application tierce | Droits minimaux, durée courte |
| Certificat client (TLS mutuel) | Échanges entre services d'une même organisation | Gestion des certificats |
Un jeton ou une clé ne passe jamais dans l'URL (?token=…) : les URL sont enregistrées dans les journaux des serveurs et des proxys, dans l'historique du navigateur, et transmises dans l'en-tête Referer. Et tout passe en HTTPS.
BOLA et BOPLA : filtrer les objets et les propriétés
BOLA est le nom de l'IDOR dans le monde des API : on corrige en vérifiant le propriétaire à chaque accès, comme au chapitre 3.
BOPLA touche les propriétés d'un objet. En lecture, l'API renvoie plus de champs que nécessaire, en comptant sur le front pour ne pas les afficher :
// VULNÉRABLE : renvoie toute la ligne, y compris mdp_hash
router.get('/api/moi', authentifier, async (req, res) => {
const [lignes] = await pool.execute('SELECT * FROM utilisateur WHERE id = ?', [req.utilisateur.id]);
res.json(lignes[0]);
});
L'outil de développement du navigateur montre la réponse complète : l'empreinte du mot de passe, des champs internes. La correction sélectionne explicitement les colonnes, ou passe par un objet de réponse dédié (un DTO, Data Transfer Object) :
const [lignes] = await pool.execute('SELECT id, login, email, role FROM utilisateur WHERE id = ?', [req.utilisateur.id]);
res.json(lignes[0]);
En écriture, c'est l'affectation de masse : l'API accepte des champs que l'utilisateur ne doit pas modifier (role, actif, montant_valide). La parade est la liste blanche des champs modifiables, par exemple avec un schéma de validation (chapitre 7).
Limiter la consommation de ressources
Une API sans limites se fait épuiser : par un attaquant, par un script mal écrit, ou par la facture d'un service payant appelé à chaque requête (envoi de SMS, géocodage). Les limites à poser :
| Ressource | Limite |
|---|---|
| Nombre de requêtes | Par client (adresse IP, jeton, clé d'API) et par fenêtre de temps ; plus stricte sur la connexion, l'envoi de mails ou de SMS. Réponse 429 Too Many Requests |
| Taille des requêtes | Corps JSON (express.json({ limit: '100kb' })), fichiers envoyés (client_max_body_size dans nginx, option limits de multer) |
| Taille des réponses | Pagination obligatoire avec un maximum : ?page=2&limite=50, et limite plafonnée côté serveur |
| Durée | Délai maximal des requêtes vers la base et vers les services tiers |
| Opérations coûteuses | Génération de PDF, export, recherche plein texte : files d'attente et quotas |
const limite = Math.min(Number.parseInt(req.query.limite, 10) || 20, 100); // 20 par défaut, 100 au maximum
const page = Math.max(Number.parseInt(req.query.page, 10) || 1, 1);
const decalage = (page - 1) * limite;
// query : avec execute, mysql2 refuse des nombres pour LIMIT ? sur MySQL 8 ; les valeurs restent échappées
const [fiches] = await pool.query(
'SELECT id, mois, etat, montant_valide FROM fiche_frais WHERE id_visiteur = ? ORDER BY mois DESC LIMIT ? OFFSET ?',
[req.utilisateur.id, limite, decalage]
);
limite et decalage sont des entiers calculés par le serveur, bornés : impossible de demander un million de lignes.
Méthodes HTTP et codes de retour
Chaque méthode a un sens, et les méthodes qui modifient (POST, PUT, PATCH, DELETE) appellent les contrôles les plus stricts. Le sujet E7 SLAM 2025 demandait justement pourquoi DELETE et PUT sont dangereuses : elles modifient ou suppriment des données existantes, ce qui atteint l'intégrité et la disponibilité, parfois sans retour possible (corrigé du sujet 2025).
| Code | Sens | Exemple |
|---|---|---|
200 OK, 201 Created, 204 No Content | Succès | Lecture, création, suppression |
400 Bad Request | Requête invalide | Champ manquant, type incorrect |
401 Unauthorized | Non authentifié | Jeton absent ou expiré |
403 Forbidden | Authentifié, non autorisé | Mauvais rôle |
404 Not Found | Introuvable | Ou ressource d'un autre, qu'on ne veut pas révéler |
405 Method Not Allowed | Méthode non prévue sur cette route | DELETE /api/fiches |
409 Conflict | État incompatible | Valider une fiche déjà validée |
429 Too Many Requests | Limite de débit dépassée | Trop de tentatives |
500 Internal Server Error | Erreur serveur | Message générique, détail dans le journal |
Une route qui n'existe que pour une méthode ne doit pas en accepter d'autres : un routeur bien configuré répond 404 ou 405 aux méthodes non déclarées.
Inventorier les versions et les routes
Une API évolue : /api/v1/, puis /api/v2/. L'ancienne version, conservée « pour les vieux clients », n'a souvent pas reçu les corrections de sécurité de la nouvelle. Une route de test, une route de débogage ou une documentation interactive (Swagger UI) oubliée en production sont autant de portes ouvertes.
Les bonnes pratiques : une documentation OpenAPI à jour, qui sert d'inventaire de toutes les routes ; une date de fin annoncée pour chaque ancienne version, puis sa suppression ; pas de route de débogage ni de documentation interactive exposée en production sans authentification ; un environnement de préproduction qui ne partage pas les données de production. Le cours JavaScript et Node.js montre la construction d'une API REST avec Express.
Consommer une API tierce sans lui faire confiance
L'API d'AppliFrais appelle un service partenaire de calcul des indemnités kilométriques. Les données qu'il renvoie sont des entrées comme les autres : elles peuvent être fausses, malveillantes si le partenaire est compromis, ou absentes s'il est en panne.
- Valider la réponse (types, plages, format) avant de l'utiliser ou de l'enregistrer.
- Encoder à l'affichage ce qui vient du tiers, comme une saisie d'utilisateur.
- Appeler le tiers en HTTPS, avec un délai maximal, et prévoir le comportement en cas de panne (échouer proprement, chapitre 12).
- Ne pas suivre aveuglément les redirections renvoyées par le tiers.
Les risques des applications mobiles
Une application mobile tourne sur un appareil que l'éditeur ne contrôle pas : le téléphone peut être perdu, volé, partagé, modifié (rooté ou jailbreaké), et le code de l'application peut être décompilé. L'OWASP Mobile Top 10 (édition 2024) liste les risques dominants :
| Code | Risque | Exemple |
|---|---|---|
| M1 | Improper Credential Usage : identifiants mal utilisés | Clé d'API écrite dans le code de l'application, mot de passe stocké en clair |
| M2 | Inadequate Supply Chain Security | Kit de développement tiers vulnérable ou malveillant |
| M3 | Insecure Authentication/Authorization | Contrôle des droits fait seulement dans l'application, pas sur le serveur |
| M4 | Insufficient Input/Output Validation | Injection SQL dans la base SQLite locale |
| M5 | Insecure Communication | Échanges en HTTP, certificat non vérifié |
| M6 | Inadequate Privacy Controls | Collecte excessive de données personnelles, journaux bavards |
| M7 | Insufficient Binary Protections | Application facile à décompiler et à modifier |
| M8 | Security Misconfiguration | Mode débogage actif dans la version publiée, sauvegarde des données autorisée |
| M9 | Insecure Data Storage | Données sensibles en clair sur l'appareil |
| M10 | Insufficient Cryptography | Algorithmes faibles, clés codées en dur |
Pour vérifier une application mobile, l'OWASP publie aussi le MASVS (exigences de sécurité) et le MASTG (guide de test), l'équivalent mobile de l'ASVS et du WSTG.
Stocker des données sur le téléphone
Aucun secret dans le code de l'application. Un fichier APK (Android) se décompile en quelques minutes avec des outils gratuits comme jadx : une clé d'API ou un mot de passe écrits dans le code sont lisibles par tous. Une application mobile est un client public : elle ne peut pas garder de secret. Les appels qui exigent une clé privée passent par ton serveur, qui détient la clé.
Les données sensibles dans le stockage sécurisé du système. Android fournit le Keystore, iOS le Trousseau (Keychain) et la Secure Enclave : des espaces où les clés cryptographiques sont générées et utilisées sans jamais pouvoir être extraites, souvent dans une puce dédiée. Un jeton de rafraîchissement ou une clé de chiffrement de la base locale y ont leur place. Les préférences ordinaires (SharedPreferences sur Android, UserDefaults sur iOS) et les fichiers en clair ne conviennent pas aux secrets.
Le minimum de données sur l'appareil, effacées à la déconnexion, et exclues des sauvegardes automatiques quand elles sont sensibles.
Les requêtes sur la base locale SQLite sont préparées, comme sur un serveur : le sujet de 2024 demandait d'écrire une insertion paramétrée dans une table de journal SQLite sur Android.
Authentification biométrique
Le sujet E6 SLAM 2024 (aujourd'hui E7) portait sur une application mobile de pompiers qui déverrouillait l'accès par biométrie, avec un journal des tentatives (corrigé du sujet 2024, dossier C). Trois idées à maîtriser.
L'application ne voit jamais l'empreinte ou le visage. Elle demande au système d'exploitation d'authentifier l'utilisateur, avec BiometricPrompt sur Android ou LocalAuthentication sur iOS. Le système compare l'empreinte au modèle enregistré dans la puce sécurisée de l'appareil et répond seulement « succès » ou « échec ».
La biométrie doit débloquer quelque chose. Un simple booléen « succès » dans le code de l'application se contourne sur un téléphone modifié. La bonne pratique lie l'authentification à une clé du Keystore créée avec l'exigence d'authentification de l'utilisateur : sans empreinte valide, la clé reste inutilisable, et le jeton qu'elle chiffre aussi. L'extrait Java simplifié suivant montre le principe :
BiometricPrompt.PromptInfo infos = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Déverrouiller AppliFrais")
.setNegativeButtonText("Utiliser le mot de passe") // une alternative est toujours proposée
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
.build();
BiometricPrompt invite = new BiometricPrompt(this, executeur, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult resultat) {
Cipher dechiffreur = resultat.getCryptoObject().getCipher(); // débloqué par l'empreinte
String jeton = dechiffrerJeton(dechiffreur); // jeton stocké chiffré sur l'appareil
journal.ajouter(new EntreeLog("LoginBiometrie", "Succes", "OK"));
ouvrirAccueil(jeton);
}
@Override
public void onAuthenticationError(int code, @NonNull CharSequence message) {
journal.ajouter(new EntreeLog("LoginBiometrie", "Echec", code + " - " + message));
}
});
invite.authenticate(infos, new BiometricPrompt.CryptoObject(dechiffreurInitialise));
La biométrie est une donnée sensible au sens du RGPD. Les données biométriques traitées pour identifier une personne de façon unique font partie des catégories particulières de données (article 9), dont le traitement est interdit par principe, sauf exceptions encadrées. Quand le modèle reste sur l'appareil, sous le seul contrôle de l'utilisateur, et que l'application n'en reçoit qu'un résultat, le risque est bien moindre qu'avec une base centrale d'empreintes. Mais dans un contexte professionnel, l'employeur doit justifier le recours à la biométrie, proposer une alternative (code, mot de passe), informer les personnes et mener une analyse d'impact si le traitement présente un risque élevé. La CNIL a publié un règlement type sur la biométrie sur les lieux de travail.
Communications de l'application mobile
- HTTPS obligatoire. Depuis Android 9, une application qui cible une version récente du système refuse par défaut le trafic en clair ; la configuration de sécurité réseau (
network_security_config.xml) ne doit pas le réautoriser. Sur iOS, App Transport Security impose HTTPS par défaut. - Ne jamais désactiver la vérification des certificats, même « temporairement pour les tests » : ce code finit en production.
- L'épinglage de certificat (certificate pinning), qui n'accepte que le certificat ou la clé attendus du serveur, protège contre une autorité de certification compromise, mais complique le renouvellement des certificats : à réserver aux applications sensibles, avec une clé de secours.
- Le serveur reste le juge. Tous les contrôles d'accès faits dans l'application mobile doivent être refaits par l'API : une application modifiée ou un script peuvent appeler l'API directement.
Exercice 1 sur 4 Classer dans l'API Top 10 Réussi À revoir
Donne le code de l'OWASP API Security Top 10 (2023) correspondant à chaque constat.
GET /api/visiteurs/12renvoie le profil complet avecmdp_hashetiban.GET /api/fiches?limite=999999renvoie toute la table.DELETE /api/utilisateurs/12fonctionne avec un jeton de visiteur./api/v1/fiches, remplacée par la v2, renvoie toujours les fiches sans authentification.- Un visiteur lit la fiche 1188 qui ne lui appartient pas.
Voir la réponseMasquer la réponse de l'exercice 1
- API3:2023 Broken Object Property Level Authorization : des propriétés sensibles sont exposées.
- API4:2023 Unrestricted Resource Consumption : pas de plafond de pagination.
- API5:2023 Broken Function Level Authorization : une fonction d'administration accessible à un rôle qui n'y a pas droit.
- API9:2023 Improper Inventory Management : une ancienne version oubliée.
- API1:2023 Broken Object Level Authorization (BOLA).
Tu avais trouvé ?
Exercice 2 sur 4 Sécuriser une route DELETE Réussi À revoir
Voici la route de suppression d'une ligne de frais :
router.delete('/api/lignes/:id', async (req, res) => {
await pool.execute('DELETE FROM ligne_frais WHERE id = ?', [req.params.id]);
res.status(204).end();
});
- Liste les contrôles manquants.
- Réécris la route : seul le visiteur propriétaire peut supprimer une ligne, et seulement si la fiche est en cours de saisie (
CR). - Quel code renvoyer si la ligne n'existe pas ou ne peut pas être supprimée ?
Voir la réponseMasquer la réponse de l'exercice 2
- Aucune authentification ; aucun contrôle du propriétaire de la ligne ; aucun contrôle de l'état de la fiche (on pourrait supprimer une ligne d'une fiche validée ou remboursée) ; aucune journalisation de la suppression.
- Les conditions vont dans la requête elle-même :
router.delete('/api/lignes/:id', authentifier, exigerRole('visiteur'), async (req, res) => {
const [resultat] = await pool.execute(
`DELETE l FROM ligne_frais l
JOIN fiche_frais f ON f.id = l.id_fiche
WHERE l.id = ? AND f.id_visiteur = ? AND f.etat = 'CR'`,
[req.params.id, req.utilisateur.id]
);
if (resultat.affectedRows === 0) return res.status(404).json({ erreur: 'Ligne introuvable ou non modifiable' });
journaliserSecurite('suppression_ligne', req, { ressource: `ligne:${req.params.id}` });
res.status(204).end();
});
- 404 (ou 409 si l'on veut distinguer une fiche qui n'est plus modifiable, à condition que la ligne appartienne bien au demandeur). Sans jeton valide, le middleware renvoie 401 ; avec un autre rôle, 403.
Tu avais trouvé ?
Exercice 3 sur 4 Application mobile Réussi À revoir
Un développeur prépare l'application mobile d'AppliFrais. Pour chaque choix, dis s'il est acceptable et justifie.
- La clé d'API du service de cartographie (payant) est écrite dans une constante du code Java.
- Le jeton de rafraîchissement est chiffré avec une clé du Keystore qui exige une authentification biométrique.
- Après une authentification biométrique réussie, l'application met
estConnecte = trueet appelle l'API sans jeton. - Pour les tests, le développeur accepte tous les certificats TLS ; il compte retirer ce code avant la publication.
Voir la réponseMasquer la réponse de l'exercice 3
- Non : l'APK se décompile, la clé sera extraite et utilisée aux frais de l'entreprise. L'application appelle le serveur d'AppliFrais, qui appelle le service de cartographie avec la clé.
- Oui : c'est la bonne pratique. Le jeton est inutilisable sans l'empreinte, même si les fichiers de l'application sont copiés.
- Non : le booléen se contourne sur un téléphone modifié, et l'API doit authentifier chaque appel. La biométrie doit débloquer un secret (le jeton), qui est ensuite présenté à l'API.
- Non : ce code a toutes les chances d'arriver en production et rend l'application vulnérable à l'interception (M5). Pour les tests, on installe sur l'appareil de test le certificat de l'autorité de développement, sans toucher à la vérification.
Tu avais trouvé ?
Exercice 4 sur 4 Biométrie et RGPD Réussi À revoir
Le laboratoire GSB veut que les visiteurs déverrouillent l'application mobile par reconnaissance faciale.
- Pourquoi cette donnée demande-t-elle des précautions particulières ?
- Quelle différence de risque y a-t-il entre utiliser la reconnaissance faciale du téléphone (BiometricPrompt) et envoyer une photo du visage au serveur pour comparaison ?
- Quelles obligations l'employeur doit-il respecter ?
Voir la réponseMasquer la réponse de l'exercice 4
- Une donnée biométrique traitée pour identifier une personne de façon unique est une catégorie particulière de données au sens de l'article 9 du RGPD : son traitement est interdit par principe, sauf exception. Contrairement à un mot de passe, elle ne se change pas si elle fuit.
- Avec BiometricPrompt, le modèle du visage reste dans la puce sécurisée du téléphone, sous le contrôle de l'utilisateur ; l'application et le serveur ne reçoivent qu'un résultat. Envoyer une photo crée une base centrale de visages : une fuite toucherait tous les visiteurs, de façon irréversible. Le premier choix est bien moins risqué.
- Justifier la nécessité et la proportionnalité du dispositif, proposer une alternative (mot de passe ou code) sans pénaliser ceux qui refusent, informer les salariés, inscrire le traitement au registre, mener une analyse d'impact (AIPD) si le traitement présente un risque élevé, et respecter le cadre fixé par la CNIL pour la biométrie au travail.
Tu avais trouvé ?