PrestaShop Admin API : sécuriser secret et scopes par client

Protégez vos intégrations PrestaShop 9.x : générer et stocker des client_secret sécurisés, implémenter scopes par client, limiter le rayon d’impact et automatiser la rotation.

Écran d'ordinateur avec du code, clients A et B, et un cadenas symbolisant la sécurité.

Table des matières :

  1. Modèle de menace : fuites de client_secret et dérive de privilèges
  2. Admin API PrestaShop : clients OAuth2, tokens et scopes (ce que le cœur fait… et ce qu’il ne fait pas)
  3. Génération et stockage du secret : entropie, hachage, rotation (sans se tirer une balle)
  4. Scopes par client : design minimal viable, mapping ressources, enforcement API Platform/Symfony
  5. Séparation des clients en intégration ERP : isoler les flux et réduire le blast radius
  6. Durcissement opérationnel : TLS, rate limiting, logs, CI/CD et réponse à incident
  7. Checklist mise en prod (PrestaShop 9.x) : secrets + scopes par client, vérifiable et testable

Modèle de menace : fuites de client_secret et dérive de privilèges

Sur l’Admin API PrestaShop (à partir de PrestaShop 9.x), le couple client_id/client_secret est un point de rupture unique : si le secret fuit, l’attaquant n’a pas besoin d’un compte back-office, il lui suffit d’obtenir un token et de parler à l’API avec les mêmes droits que l’intégration légitime. Dans les incidents réels, la fuite n’arrive presque jamais via un “hack sophistiqué” : elle vient de secrets commités dans Git, de variables d’environnement dumpées dans un log de débogage, de dumps SQL exportés sans chiffrement, ou d’un iPaaS (Make/Zapier/ETL) qui stocke le secret côté SaaS sans rotation.

Pour rendre le risque concret, voici les sources de fuite les plus fréquentes sur des piles e‑commerce (celles qu’on retrouve en audit) :

  • CI/CD : logs de pipeline contenant un curl avec en-tête Authorization, artefacts de build incluant un .env, variables masquées mal configurées.
  • Observabilité : traces APM qui capturent les en-têtes (tokens), dumps d’exception Symfony/Monolog en niveau debug, export JSON « pour le support » envoyé par e‑mail.
  • Intégrations SaaS : connecteurs où le secret est saisi dans une UI et conservé côté fournisseur, sans rotation ni contrôle d’accès fin.
  • Backups : snapshots de base/restic non chiffrés, exports pour migration déposés sur un stockage partagé.
  • Postes de travail : secrets copiés‑collés dans un wiki interne, un outil de ticketing, ou un chat (Slack/Teams) — c’est trivial, mais très courant.

La seconde classe de problème est la dérive de droits : vous commencez avec un connecteur “lecture produits”, et au fil des sprints il finit avec un scope qui permet l’écriture sur commandes, clients et catalogues. Ce glissement est structurel si vous n’imposez pas une granularité de scopes par client (principe du moindre privilège) et si vous n’avez pas de mécanisme d’audit pour répondre à une question simple : “Quel client a le droit de modifier quoi, et depuis quand ?”.

Un bon réflexe consiste à formaliser, dès le départ, le “blast radius” acceptable. Exemple d’objectif opérationnel : “si le secret du PIM fuit, l’attaquant peut au pire lire le catalogue et les stocks, mais ne peut ni créer une commande, ni modifier un client, ni toucher aux prix”. Sans cette cible, on finit presque toujours par donner “un peu plus” de droits “pour débloquer”, puis on oublie de retirer.

Enfin, la réalité PrestaShop : le cœur est historiquement permissif sur certains aspects (héritage Webservice, modules qui contournent la logique de permissions, hooks qui réécrivent des comportements). Donc si vous ajoutez l’Admin API sans verrouiller secrets + scopes + contrôle d’accès objet (BOLA), vous multipliez les surfaces d’attaque. OWASP résume l’objectif de base : limiter l’impact d’une compromission en cloisonnant les droits au plus près de chaque usage (notamment contre les classes de risques “Broken Object Level Authorization” et “Broken Function Level Authorization” côté API).

“The client identifier is not a secret; it is exposed to the resource owner and MUST NOT be used alone for client authentication.” — IETF, RFC 6749 (OAuth 2.0), section 2.2

Admin API PrestaShop : clients OAuth2, tokens et scopes (ce que le cœur fait… et ce qu’il ne fait pas)

Sur PrestaShop 9.x (PHP 8.2/8.3 recommandé), l’Admin API s’inscrit dans une logique “API‑first” adossée à Symfony et à une exposition type API Platform. Si vous n’avez pas encore un environnement propre pour itérer sans casser la prod, partez d’une configuration reproductible (Docker + Swagger) : voir PrestaShop Admin API : environnement de développement Docker et Swagger.

Dans un modèle OAuth2 standard, un “client” représente une application (ERP, PIM, WMS, middleware, module interne, outil BI). Le client_secret authentifie un client confidentiel (serveur à serveur). Les scopes sont une liste de permissions déclaratives portées par le token (p. ex. orders:read, orders:write, products:read). En pratique, la qualité de sécurité dépend moins du fait “d’avoir OAuth2” que de l’application stricte de deux règles : (1) secret réellement secret + rotatable, (2) scopes réellement contraignants sur chaque opération (pas seulement décoratifs).

Deux points à clarifier pour éviter les malentendus en équipe :

  • Un token valide n’est pas une autorisation : c’est une authentification réussie. L’autorisation réelle doit être réévaluée à chaque opération (scopes + règles métier + contraintes multi‑boutique).
  • “Confidential” vs “public” : un client “public” (SPA, appli mobile, script distribué) ne peut pas garder un secret. Dans ce cas, vous évitez client_secret et vous basculez vers des mécanismes adaptés (PKCE, etc.). Le risque classique : un “petit script JS” déployé côté navigateur qui embarque un secret → fuite garantie.

Le piège fréquent sur des piles e‑commerce : on confond “authentification” (obtenir un token valide) et “autorisation” (être autorisé à accéder à cet objet ou à exécuter cette action). Les scopes répondent à la seconde dimension, mais ne suffisent pas à eux seuls : sur des ressources multi‑boutique ou multi‑entité, il faut aussi de l’autorisation au niveau objet (ex. un client OAuth peut lire les commandes du shop A, mais pas du shop B). C’est exactement le type de dérive à traiter dès la conception, sinon vous finissez avec des exceptions métier hardcodées.

À noter aussi : la gestion du cycle de vie des tokens (durée de vie, révocation, renouvellement) doit être cohérente avec votre modèle de risque. Même avec des scopes parfaits, un token long‑lived augmente mécaniquement la fenêtre d’exploitation si un en‑tête Authorization fuit via un log ou un proxy. Une approche robuste est : tokens courts + rotation facile du secret + capacité de révocation (au moins côté serveur, par invalidation).

“Bearer tokens MUST be protected from disclosure in storage and in transport.” — IETF, RFC 6750 (Bearer Token Usage), section 5

Génération et stockage du secret : entropie, hachage, rotation (sans se tirer une balle)

Côté génération, arrêtez les secrets “humains” : un client_secret doit être un jeton aléatoire à haute entropie. En PHP 8.2/8.3, le minimum sérieux c’est random_bytes(32) (256 bits) encodé en Base64URL. Exemple (pseudo‑code) :

$secret = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');

Pourquoi Base64URL ? Parce que c’est copiable‑collable dans des outils (CI, Vault, variables d’environnement) sans casser des parseurs, et sans caractères “spéciaux” souvent mal échappés.

Un secret court ou généré via un pseudo‑random (ou pire, un mot de passe) est une invitation aux attaques offline si votre base fuit. Sur un SI e‑commerce, ne sous‑estimez pas les fuites “silencieuses” (export SQL pour prestataire, sauvegarde sur NAS, snapshot cloud mal paramétré) : si le secret est faible, l’attaquant n’a pas besoin d’être “sur votre infra” pour le casser.

Côté stockage, le pattern robuste en 2026 est : ne stocker que le haché du secret (Argon2id ou bcrypt), comme un mot de passe. Oui, ça veut dire que vous ne pourrez pas “ré‑afficher” le secret dans le back‑office : vous ne l’affichez qu’une fois à la création, puis vous forcez une rotation en cas de perte. Concrètement : password_hash($secret, PASSWORD_ARGON2ID) en base, et password_verify() lors de l’authentification du client. Ça protège contre les fuites de dumps SQL et contre un accès en lecture à la base de données.

Deux précisions pratiques (souvent oubliées) :

  • Journalisation : ne consignez jamais le secret (même partiellement) dans les logs applicatifs. Évitez aussi les “aides au débogage” du type “secret invalide : reçu X”. Vous devez consigner le client_id, pas le secret.
  • Séparation des responsabilités : si votre Admin API et votre base de données sont sur le même périmètre réseau, un attaquant avec un accès en lecture à la base peut aussi souvent lire la configuration. Le hachage limite l’impact, mais l’idéal reste d’ajouter une gestion de secrets externe (KMS/Secret Manager) pour limiter les exfiltrations opportunistes.

La rotation doit être prévue, pas improvisée. Le pattern opérationnel simple est d’autoriser deux secrets actifs par client pendant une fenêtre de transition (ex. current_secret_hash + previous_secret_hash avec previous_expires_at). Ça évite de “couper” un ERP en production le jour où vous changez le secret.

Définissez aussi des événements déclencheurs de rotation (au‑delà du “tous les X mois”) :

  • changement de prestataire/intégrateur,
  • suspicion de fuite (log, ticket, capture d’écran),
  • incident sur un poste (malware) d’un opérateur ayant manipulé le secret,
  • exposition accidentelle dans un repo.

Si vous avez une infra Kubernetes, externalisez la distribution côté consommateurs via un gestionnaire de secrets/KMS (Vault, AWS Secrets Manager, OVHcloud OKMS) et synchronisez en cluster (voir External Secrets Operator : synchroniser les secrets Kubernetes avec OVHcloud OKMS). Le gain n’est pas seulement “confort” : vous standardisez la rotation, vous évitez les secrets dans les manifests, et vous améliorez l’audit.

Scopes par client : design minimal viable, mapping ressources, enforcement API Platform/Symfony

Un scope utile doit être (a) verbe + ressource, (b) stable dans le temps, (c) testable. Exemple : orders:read, orders:write, customers:read, catalog:read. Évitez les scopes “fourre‑tout” (admin, full_access) sauf pour un client interne strictement contrôlé, et documentez‑le comme une exception. La compatibilité est une contrainte : retirer un scope peut casser une intégration ; ajoutez‑en de nouveaux au lieu de surcharger les anciens.

Pour éviter que vos scopes deviennent un patchwork, définissez une convention de nommage dès le départ :

  • ressources au pluriel (orders, products, customers),
  • verbes constants (read, write, éventuellement delete, import, export),
  • un séparateur unique (:),
  • pas de scopes “fonctionnels” ambigus (manage, edit_everything).

Le design “par client” consiste à associer explicitement une liste de scopes à chaque client_id (en base, ou via configuration signée). Ne déduisez pas les scopes du type d’application (ERP vs PIM) : vous finissez avec un RBAC rigide. À l’inverse, ne faites pas non plus un scope par endpoint (trop fin, impossible à maintenir). L’axe de découpe pragmatique est : domaine métier (commandes, catalogue, stock, clients, contenu) + lecture/écriture + éventuellement une dimension “bulk” (import/export) si vos endpoints d’import ont un impact plus critique.

Un exemple de mapping minimal viable (à adapter à votre modèle métier) :

Type de client Scopes recommandés (minimum) Scopes à éviter par défaut
BI / reporting orders:read, customers:read (et parfois catalog:read) tout :write, tout :import
PIM (catalogue) catalog:read, catalog:write orders:*, customers:*
ERP (flux commandes) orders:read, orders:write, stock:read customers:write si non indispensable
ETL / import massif catalog:import, stock:import (séparés), éventuellement catalog:write scopes temps réel de commandes si non requis

Côté enforcement, l’important est de bloquer au niveau le plus près de l’opération (API Platform / contrôleur / handler), pas dans une couche “service” partagée où un appel interne pourrait bypasser le check. Avec API Platform, on peut utiliser des expressions de sécurité par opération. Exemple conceptuel :

#[Get(
  uriTemplate: '/orders/{id}',
  security: "is_granted('SCOPE_orders:read')"
)]
class OrderResource {}

Ensuite, vous implémentez un Voter (ou un AuthorizationChecker) qui mappe SCOPE_orders:read vers le claim scope/scopes du token. Si votre token est un JWT, vous pouvez porter les scopes dans un claim standard (scope espace‑séparé) ou custom (scopes tableau). Le point non négociable : un endpoint sans règle explicite doit être considéré comme une régression (test automatisé).

Une pratique efficace en équipe : rendre visible ce contrat via des tests d’intégration “no‑scope / wrong‑scope”, par exemple :

  • GET /orders/{id} avec token sans orders:read → 403
  • POST /orders avec token orders:read → 403
  • GET /orders/{id} avec orders:read mais hors périmètre boutique (voir ci‑dessous) → 403

Les scopes ne remplacent pas l’autorisation objet. Sur un PrestaShop en multiboutique, ajoutez une claim shop_ids dans le token, ou un couple (scope + contrainte) du type orders:read@shop:1. Sinon votre client OAuth “lecture commandes” lira tout le parc. PrestaShop 9.2 introduit des mécanismes natifs pour certaines propriétés multi‑boutique (voir PrestaShop 9.2 : Extra Properties natif pour champs multiboutique) : exploitez cette logique pour ne pas reconstruire une gestion d’isolation à la main.

Séparation des clients en intégration ERP : isoler les flux et réduire le blast radius

Dans une intégration ERP sérieuse, vous avez rarement “un seul client API”. Vous avez au minimum : (1) un client lecture catalogue/stock (ERP → PrestaShop ou l’inverse), (2) un client écriture commandes/expéditions, (3) éventuellement un client import bulk (ETL) qui a des droits élevés mais n’est utilisé que par batch. Cette séparation est ce qui limite le blast radius : si le secret du batch fuit, l’attaquant n’a pas forcément accès aux endpoints de commandes temps réel.

Un mini‑scénario réaliste : vous branchez un PIM et un ERP. Le PIM a besoin d’écrire produits, catégories, médias ; l’ERP a besoin de lire des commandes et de pousser des statuts d’expédition. Si vous mutualisez un seul client “full access”, une fuite côté PIM (prestataire, connecteur SaaS, poste de travail) devient une fuite “capable de manipuler des commandes”. Avec deux clients, vous découplez les risques : le PIM ne touche pas aux commandes, l’ERP ne touche pas au catalogue.

Sur le terrain, le découplage technique passe aussi par l’architecture : un ETL qui fait 2000 requêtes/minute ne doit pas partager les mêmes quotas, rate limits et scopes qu’un outil BI en lecture. Vous pouvez aller plus loin : isoler par IP (allowlist) et par réseau (VPN/Private Link) pour les clients “confidentiels”. Sur le pattern global d’architecture API/Webhooks/queues, appuyez‑vous sur Intégration ERP e‑commerce : architecture API, webhooks et files RabbitMQ, puis déclinez la même segmentation côté OAuth.

Deux bonnes pratiques complémentaires pour l’exploitation (souvent négligées) :

  • Un client_id par environnement (dev/staging/prod). Sinon, un token de staging ou un secret “de test” finit par tourner en prod.
  • Un client_id par acteur quand c’est possible (ERP éditeur A, prestataire B, module interne C). Ça simplifie l’attribution lors d’un audit : “qui a fait cet appel ?”.

Enfin, arrêtez d’utiliser des clés “super‑admin” pour déboguer : c’est précisément comme ça que les secrets finissent dans des tickets ou des captures d’écran. Créez un client temporaire avec un scope minimal de diagnostic (health:read, metrics:read), valable 24 h, et supprimez‑le. Si vous devez exposer un CRUD complet, documentez‑le et traitez‑le comme un compte de service (audit, rotation, journalisation).

Durcissement opérationnel : TLS, rate limiting, logs, CI/CD et réponse à incident

Le transport n’est pas optionnel : TLS obligatoire, HSTS côté domaine, et interdiction absolue d’envoyer des tokens via URL (query string) ou de les logger. Dans Nginx/Apache, filtrez les en‑têtes sensibles (Authorization) des logs d’accès. Ajoutez une politique de sécurité côté serveur PHP/Symfony (désactivation d’erreurs en prod, durcissement des en‑têtes) : voir Symfony : sécuriser un serveur PHP avec hardening et gestion CVE.

Exemple opérationnel (côté reverse‑proxy) : votre objectif est que même en cas de niveau de logs trop verbeux, aucun token ne se retrouve dans un index de logs. Cela implique aussi de vérifier les outils en amont : WAF/CDN, APM, proxies, et collectors qui auraient un mode “capture headers”.

Ajoutez du rate limiting par client (et idéalement par endpoint) : un client compromis doit être contenable sans impacter le reste. Si vous avez des enjeux de performance, anticipez la pagination, les index et le pool de connexions côté DB (voir Performance API : index, pagination et pool de connexions). Le rate limit n’est pas qu’un sujet DDoS : c’est aussi un frein à l’exfiltration lente (par exemple “lire toutes les commandes de l’année” à bas bruit).

La sécurité devient exploitable quand vous avez de l’observabilité. Tracez : client_id, scopes accordés, endpoint, statut, latence, et un identifiant de corrélation. Centralisez vos logs (ex. OpenSearch) pour corréler une montée de 401/403 et détecter du bruteforce de secret ou de token (voir OVHcloud Managed OpenSearch : moteur NoSQL managé pour logs et recherche).

Ajoutez aussi des alertes “simples mais efficaces”, typiquement :

  • hausse soudaine des 401 sur /token (secret erroné, tentative de bruteforce, config cassée),
  • hausse des 403 sur un endpoint sensible (token volé mais scopes insuffisants, ou intégration déployée avec mauvais profil),
  • volume anormal sur des endpoints de lecture (exfiltration),
  • appels à des heures atypiques pour un batch.

Et si vous ne faites pas déjà SBOM + scans + WAF, vous vous privez d’une couche de réduction de risque sur l’écosystème modules (voir PrestaShop : sécuriser APIs et modules avec SBOM, WAF et CI/CD).

Côté conformité (France/UE), souvenez‑vous que vos logs d’API peuvent rapidement contenir des données liées à des clients finaux (IDs, emails selon endpoints, adresses, etc.). Appliquez un principe de minimisation et une rétention maîtrisée ; le guide de la CNIL sur la sécurité des données personnelles est une base utile pour cadrer ces pratiques (en particulier sur la journalisation, le contrôle d’accès et la gestion des habilitations) : Guide CNIL — sécurité des données personnelles

En cas de fuite de secret, le plan doit être écrit : (1) rotation immédiate (activer le secret de secours), (2) révocation des tokens (si vous avez un store de tokens / blacklist), (3) réduction temporaire des scopes à un profil minimal, (4) analyse forensique. Sans procédure, vous perdez des heures et vous aggravez l’impact. Pour cadrer ce volet, voir Sécurité PrestaShop : plan de réponse à incident et containment immédiat.

Checklist mise en prod (PrestaShop 9.x) : secrets + scopes par client, vérifiable et testable

Avant de toucher à la prod, faites‑le proprement : staging fidèle, sauvegarde DB, et tests d’intégration. Si vous êtes sur une migration ou un changement de version, appliquez une checklist de mise à jour (voir Mise à jour PrestaShop : checklist sauvegarde, pré-production et tests). Les changements d’auth/autorisation ont une propriété : ils cassent vite, et souvent en dehors des heures ouvrées.

Checklist “secrets” : (1) génération via random_bytes, longueur ≥ 32 bytes, (2) stockage haché (Argon2id), (3) double‑secret pour rotation, (4) aucun secret en clair dans la DB, les logs, les exports, (5) rotation planifiée (trimestrielle ou à chaque changement d’intégrateur), (6) distribution via KMS/Secret Manager (ou a minima variables d’environnement gérées côté infra), (7) suppression des clients dormants.

Checklist “scopes” : (1) scopes explicitement attachés à chaque client, (2) opération API Platform protégée par une expression de sécurité (pas de “par défaut autorisé”), (3) tests automatisés : pour chaque endpoint critique, un test “scope absent → 403”, (4) autorisation objet/multiboutique traitée (shop_ids / contraintes), (5) journalisation client_id + scopes + endpoint, (6) audit régulier (revue des scopes accordés vs besoins réels).

Pour rendre la checklist vérifiable (et pas juste déclarative), ajoutez des “preuves attendues” :

  • une liste versionnée des clients OAuth (qui, quoi, pourquoi, date),
  • un export (ou écran) montrant scopes par client + date de dernière utilisation,
  • un rapport CI qui valide : “endpoint sensible sans règle de sécurité = échec”,
  • une procédure de rotation testée (exercice en staging) et une procédure de révocation (même si minimale).

Références externes utiles (standards et bonnes pratiques) :


À lire aussi