On ne perd presque jamais son back-office par hasard : derrière, il y a eu une bascule en HTTPS, une sauvegarde restaurée ou un domaine qu’on a changé, bref une opération de tous les jours. Ça se manifeste par un jeton invalide, ou par un va-et-vient sans fin qui vous repose devant l’écran de connexion. Et plus personne ne rentre.
Rassurez-vous, la liste des causes tient sur une main, et chacune se vérifie en base, une par une. Je vous déroule ma propre procédure, valable sur une 1.7, une 8.x ou une 9.x (toujours dans cet ordre), et si la porte ne s’ouvre toujours pas, rien ne m’empêche de reprendre la main moi-même sur la boutique.
Comment le jeton d’administration est construit
Savoir comment ça marche vous évitera de fouiller là où il n’y a rien. À chaque requête, le jeton est refabriqué depuis trois éléments stables, et pas un gramme de hasard là-dedans.
Trois ingrédients, une empreinte
Le cœur ramasse trois choses, le contrôleur visé par son nom, l’onglet correspondant par son identifiant, l’employé connecté par le sien, et hache l’ensemble avec la cookie_key rangée dans le fichier de configuration. Déplacez un seul de ces éléments et le jeton recalculé ne collera plus à celui qu’affiche votre URL. Et vous voilà dehors.
Pourquoi l’URL de la boutique entre en jeu
Votre session voyage dans un cookie attaché à un couple domaine plus chemin bien précis, que le cœur va lire dans ps_shop_url. Tapez une autre adresse que celle enregistrée et le navigateur garde le cookie pour lui, l’employé cesse d’être reconnu, le jeton tombe en panne (juste après une migration, rien ne revient aussi souvent).
Vérifier l’URL enregistrée en base
Migration, ou simple changement de domaine, commencez toujours par là. Deux endroits portent l’information (deux tables, pas une). Et les deux doivent concorder.
Les colonnes qui comptent
Du côté de ps_shop_url, domain comme domain_ssl n’attendent que le nom d’hôte, rien devant, pas de barre oblique derrière. physical_uri porte de son côté le chemin d’installation bordé d’une barre oblique de part et d’autre, ramené à une seule barre si la boutique est à la racine. Relevez ces trois valeurs quelque part avant de modifier quoi que ce soit, je le fais systématiquement.
/* Préfixe ps_ par défaut : remplacez-le par le préfixe réel de votre boutique. */
/* Le domaine se saisit sans protocole et sans barre oblique finale.
physical_uri vaut / pour une installation à la racine, /boutique/ pour
une installation dans un sous-dossier, barres obliques comprises. */
SELECT su.id_shop_url,
su.id_shop,
su.domain,
su.domain_ssl,
su.physical_uri,
su.virtual_uri,
su.main,
su.active,
s.name AS boutique,
s.active AS boutique_active
FROM ps_shop_url su
INNER JOIN ps_shop s ON s.id_shop = su.id_shop
ORDER BY su.id_shop, su.main DESC;
/* Les mêmes valeurs sont recopiées dans la table de configuration.
Un écart entre les deux provoque des redirections en boucle. */
SELECT name, value
FROM ps_configuration
WHERE name IN ('PS_SHOP_DOMAIN', 'PS_SHOP_DOMAIN_SSL', 'PS_SSL_ENABLED', 'PS_SSL_ENABLED_EVERYWHERE');
Corriger sans casser les contraintes
La combinaison domaine plus chemins est verrouillée par deux index uniques, un pour la version simple du domaine, un pour sa version SSL. Avec plusieurs boutiques, une correction trop large se fait jeter avec un message clair, au lieu de créer un doublon silencieux en douce (ça tombe bien). Je fais quand même une copie de la table avant d’écrire, faites pareil, le retour arrière ne vous coûtera qu’une commande.
/* Sauvegardez la table avant toute écriture :
mysqldump -u UTILISATEUR -p NOM_BASE ps_shop_url > shop_url.sql */
UPDATE ps_shop_url
SET domain = 'www.exemple.fr',
domain_ssl = 'www.exemple.fr',
physical_uri = '/',
virtual_uri = ''
WHERE id_shop = 1 AND main = 1;
/* Alignement de la configuration sur la même valeur. */
UPDATE ps_configuration
SET value = 'www.exemple.fr', date_upd = NOW()
WHERE name IN ('PS_SHOP_DOMAIN', 'PS_SHOP_DOMAIN_SSL');
/* Deux contraintes uniques portent sur le trio domaine, physical_uri,
virtual_uri. Une erreur 1062 signale qu'une autre boutique occupe déjà
cette combinaison : corrigez-la avant de réessayer. */
La clé cookie_key et ce qu’elle scelle
Les blocages que je récupère viennent souvent d’une restauration faite à moitié, avec des fichiers pris dans un environnement et une base prise dans un autre. Prise à part, chaque moitié se tient. Entre elles, plus rien ne se parle. Du coup, aucune porte ne s’ouvre.
Un seul fichier depuis la 1.7
Allez la chercher dans app/config/parameters.php, à l’entrée cookie_key, voisine de cookie_iv et de new_cookie_key (une seule vous intéresse, la première). Quant à config/settings.inc.php, il date de la branche 1.6 et aucune des versions traitées ici ne le connaît encore. Méfiance avec le bout de code ci-dessous, que je vous donne tel quel et qui affiche ces valeurs en clair : le laisser traîner à la racine, c’est vous offrir une faille PHP tout ce qu’il y a de plus classique, donc jetez-le dès la valeur notée.
<?php
/**
* Lecture des clés de chiffrement d'une installation PrestaShop 1.7, 8 ou 9.
* Déposez ce fichier à la racine de la boutique sous le nom lire-cles.php,
* lancez-le en SSH : php lire-cles.php
* Supprimez-le aussitôt, il expose des secrets.
*/
$fichier = dirname(__FILE__) . '/app/config/parameters.php';
if (!is_readable($fichier)) {
exit("Fichier introuvable ou illisible : $fichier\n");
}
$config = require $fichier;
if (!isset($config['parameters'])) {
exit("Clé parameters absente du fichier de configuration\n");
}
$parametres = $config['parameters'];
foreach (['database_name', 'database_prefix', 'cookie_key', 'cookie_iv', 'new_cookie_key'] as $cle) {
printf("%-16s : %s\n", $cle, isset($parametres[$cle]) ? $parametres[$cle] : '(absent)');
}
/* cookie_key alimente la constante _COOKIE_KEY_, qui sert de sel aux jetons
d'administration. Modifier cette valeur invalide tous les jetons en cours
et déconnecte l'ensemble des utilisateurs. Sur PrestaShop 1.6, la même
information se trouvait dans config/settings.inc.php, fichier qui n'existe
plus depuis la 1.7. */
Ce que provoque un changement de clé
Les jetons d’administration sont salés avec cette valeur, donc en changer met tous ceux qui circulent à la poubelle et referme sur-le-champ les sessions en cours. Sur les versions récentes, le mot de passe d’un employé ne bouge pas d’un poil, puisqu’il dort en bcrypt sans rien lui devoir (je l’ai vérifié moi-même en base). J’ai beau lire le contraire un peu partout.
Réparer ou recréer le compte employé
URL et clé d’accord entre elles ? Alors c’est votre compte qu’il faut regarder, parce que dans deux cas bien identifiés le cœur bloque la connexion sans vous dire pourquoi.
Le contrôle appliqué à la connexion
Quand le cœur cherche votre employé, il filtre sur active = 1, si bien qu’un compte éteint vous renvoie tout simplement le même écran qu’un identifiant qui n’existe pas. Vient ensuite le mot de passe, testé en bcrypt en premier, puis, en roue de secours, avec un MD5 que la cookie_key vient saler, réservé aux bases qui traînent depuis la 1.6.
<?php /** * Génération d'un mot de passe employé pour PrestaShop 1.7, 8 et 9. * Le coeur stocke un hash bcrypt produit par password_hash() avec * l'algorithme PASSWORD_BCRYPT. Le MD5 salé par la cookie_key n'est plus * qu'une méthode de repli, conservée pour relire les bases venues de la 1.6. * * Déposez ce fichier où vous voulez, y compris hors de la boutique : * php generer-hash.php */ $motDePasse = 'RemplacezCeMotDePasse2026'; $hash = password_hash($motDePasse, PASSWORD_BCRYPT); echo "Mot de passe : " . $motDePasse . PHP_EOL; echo "Hash bcrypt : " . $hash . PHP_EOL; echo "Contrôle : " . (password_verify($motDePasse, $hash) ? 'OK' : 'ECHEC') . PHP_EOL; /* À la première connexion réussie avec un hash MD5 hérité, le coeur le remplace tout seul par un hash bcrypt. Inutile de forcer quoi que ce soit une fois l'accès retrouvé. */
Injecter le hash puis réactiver
Pour ma part, je fabrique mon hash en PHP et j’évite le MD5 de phpMyAdmin, parce que bcrypt est la méthode du moment et que ce repli-là finira bien par sauter. Une seule connexion réussie avec un hash hérité, et le cœur le repasse lui-même en bcrypt (rien de plus à faire chez vous).
/* Collez ici le hash bcrypt produit par le script précédent.
Il commence par $2y$ et fait 60 caractères. */
UPDATE ps_employee
SET passwd = '$2y$10$RemplacezParVotreHashBcryptDe60CaracteresExactement00',
active = 1,
id_profile = 1,
last_passwd_gen = NOW()
WHERE email = 'vous@exemple.fr';
/* Contrôle. active doit valoir 1, id_profile vaut 1 pour le profil
SuperAdmin, seul profil que le coeur autorise sans passer par les
tables de droits. */
SELECT e.id_employee, e.email, e.active, e.id_profile, pl.name AS profil
FROM ps_employee e
LEFT JOIN ps_profile_lang pl ON pl.id_profile = e.id_profile AND pl.id_lang = 1
ORDER BY e.id_employee;
Créer un accès de secours
Face à un compte principal qui s’entête, j’en monte un deuxième avec le profil 1, parce que le cœur ne tient que celui-là pour un administrateur complet, sans même ouvrir ses tables de droits. Et en multiboutique, impossible de zapper la ligne qui rattache votre compte dans ps_employee_shop. Maintenant, si ce compte principal s’est éteint tout seul, ou que quelqu’un a changé son mot de passe sans rien vous demander, passez le serveur au peigne fin après intrusion d’abord. N’ouvrez pas avant.
/* Compte d'administration de secours. Le hash est celui généré en PHP. */
INSERT INTO ps_employee
(id_profile, id_lang, lastname, firstname, email, passwd,
last_passwd_gen, active, bo_menu, stats_compare_option, default_tab, bo_width)
VALUES
(1, 1, 'Secours', 'Acces', 'secours@exemple.fr',
'$2y$10$RemplacezParVotreHashBcryptDe60CaracteresExactement00',
NOW(), 1, 1, 1, 0, 0);
/* Rattachement à toutes les boutiques actives, obligatoire en multiboutique. */
INSERT INTO ps_employee_shop (id_employee, id_shop)
SELECT LAST_INSERT_ID(), id_shop
FROM ps_shop
WHERE deleted = 0;
/* Connectez-vous immédiatement, réparez le compte habituel depuis
l'interface, puis supprimez ce compte de secours. */
-- DELETE FROM ps_employee_shop WHERE id_employee = 42;
-- DELETE FROM ps_employee WHERE email = 'secours@exemple.fr';
SSL, reverse proxy et boucles de redirection
Le certificat n’est presque jamais en cause. En général, je tombe sur deux étages qui redirigent chacun dans son coin, sans se parler (et c’est bien là le problème), et la balle fait des allers-retours.
Une clé de configuration de moins en version 9
Deux réglages cohabitaient en PrestaShop 8, PS_SSL_ENABLED et PS_SSL_ENABLED_EVERYWHERE, et la 9 ne retient plus que le premier. Après une migration de base, la deuxième ligne survit parfois sans que rien ne la lise (elle ne casse rien, elle induit juste en erreur), et ça explique des comportements qu’on n’arrive pas à reproduire.
Repérer qui redirige
Avant de poser un doigt sur le code, déroulez la chaîne de redirections complète, en ligne de commande. L’hébergeur envoie le HTTPS sur un hôte, PrestaShop l’envoie sur un autre, et chacun renvoie la balle à l’autre jusqu’à la fin des temps. Avec un reverse proxy devant, contrôlez en plus que l’en-tête annonçant le protocole d’origine arrive jusqu’au bout. Et si tout s’arrête sur un 500 plutôt que de tourner en rond, changez de terrain, vous basculez sur l’erreur 500 qui suit une mise à jour, et ça se règle autrement.
# Depuis la racine de la boutique.
# Purge des caches compilés, y compris le conteneur de services et Smarty
rm -rf var/cache/prod/* var/cache/dev/*
# Le serveur web doit pouvoir réécrire ces dossiers
ls -ld var/cache app/config
ls -l app/config/parameters.php
# Réponse HTTP renvoyée par le dossier d'administration.
# Une chaîne de 301 ou 302 qui boucle trahit un désaccord entre
# ps_shop_url, la configuration SSL et la redirection de l'hébergeur.
curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' \
https://www.exemple.fr/admin1234/
# Sur un site derrière un reverse proxy, PrestaShop lit l'en-tête
# X-Forwarded-Proto pour savoir s'il est en HTTPS
curl -sI https://www.exemple.fr/ | grep -i -e location -e strict-transport
Sessions en base et caches compilés
Dernière étape, et c’est celle que je zappe le plus facilement : balayez ce qui est resté en mémoire côté serveur comme côté navigateur, avant de décider que votre correction est tombée à plat.
Les tables de sessions
En base depuis la 1.7.8, les jetons de session logent dans ps_employee_session et ps_customer_session. Videz-les et plus personne n’est connecté, ni côté administration ni côté clientèle, pendant que vos paniers et vos commandes restent intacts (rien ne se perd). Après avoir corrigé une URL, ça repart sur des bases propres.
/* Les jetons de session sont stockés en base depuis PrestaShop 1.7.8. Vider ces deux tables déconnecte tout le monde sans toucher aux commandes ni aux paniers. */ SELECT 'employee' AS type, COUNT(*) AS sessions FROM ps_employee_session UNION ALL SELECT 'customer', COUNT(*) FROM ps_customer_session; DELETE FROM ps_employee_session; DELETE FROM ps_customer_session; /* Sessions d'administration ouvertes et leur ancienneté, utile avant de tout supprimer sur une boutique à plusieurs administrateurs. */ SELECT es.id_employee, e.email, es.date_add, es.date_upd FROM ps_employee_session es LEFT JOIN ps_employee e ON e.id_employee = es.id_employee ORDER BY es.date_upd DESC;
Puis vérifier côté client
Finissez par un essai en navigation privée. Ça passe là mais pas dans votre fenêtre de tous les jours ? Le fautif, c’était un cookie posé chez vous et braqué sur l’ancien domaine. Un bémol pour terminer, et j’y tiens beaucoup : tout ce que je viens de décrire exige d’avoir la base et les fichiers sous la main. Privé de FTP et de phpMyAdmin, vous ne ferez rien de tout ça, et il faudra frapper à la porte de votre hébergeur. Ces procédures-là, je les détaille sur le blog. Une rechute, en revanche, appelle plutôt un audit technique de fond.
Questions fréquentes
Pourquoi mon back-office affiche-t-il un jeton invalide ?
Le jeton se refabrique à chaque requête depuis le contrôleur visé, l’identifiant de l’onglet, celui de l’employé, plus la valeur cookie_key. Il saute au moindre de ces éléments qui change, ou dès que votre navigateur garde pour lui son cookie de session, le domaine inscrit dans ps_shop_url ne collant plus à l’adresse que vous tapez. Mettez donc ces deux valeurs côte à côte pour commencer.
Comment réinitialiser un mot de passe employé directement en base ?
Passez par password_hash($motDePasse, PASSWORD_BCRYPT) en PHP pour obtenir un hash bcrypt, à écrire ensuite dans ps_employee, colonne passwd. Les trois branches, 1.7, 8 et 9, tournent sur cet algorithme. La vieille recette, un MD5 de la cookie_key collée au mot de passe, passe encore en deuxième essai sur les bases qui remontent à la 1.6, et dès la première connexion qui aboutit, ce hash se fait troquer contre un bcrypt.
Comment réactiver un compte administrateur bloqué ?
La connexion passe par une requête qui filtre sur active = 1, donc un compte éteint vous sert la même réponse qu’un identifiant inconnu, et pas un mot de plus. Repassez active à 1 sur la bonne ligne de ps_employee. Toujours bloqué ? Créez-vous alors un accès de secours, id_profile sur 1 c’est-à-dire le profil SuperAdmin, et pensez à sa ligne dans ps_employee_shop boutique par boutique.
Quelles valeurs mettre dans domain et physical_uri ?
domain veut le nom d’hôte et rien d’autre, sans https://, sans barre oblique finale, du genre www.exemple.fr. Idem pour domain_ssl, à moins que votre HTTPS ne sorte d’un autre hôte. physical_uri veut le chemin d’installation bordé d’une barre oblique de chaque côté, réduit à une seule barre si vous êtes à la racine. Et remettez d’équerre PS_SHOP_DOMAIN ainsi que PS_SHOP_DOMAIN_SSL du côté de votre configuration (tous les deux, pas juste un).
Où se trouve la cookie_key sur les versions récentes ?
Du côté de app/config/parameters.php, sous la clé cookie_key, à l’intérieur du tableau parameters. Vous y verrez voisiner cookie_iv et new_cookie_key. Le chemin config/settings.inc.php, que je croise encore dans un paquet de tutoriels, relève de la 1.6 et s’est volatilisé dès la 1.7. Contrôlez enfin que le serveur web arrive à lire ce fichier, parce que des droits mal remis après restauration donnent exactement la même impression qu’une clé absente.
Faut-il vider les tables de sessions ?
Oui, après avoir corrigé une URL ou remplacé une clé, ça sert. Les jetons, eux, se logent dans ps_employee_session et ps_customer_session depuis la 1.7.8. Les vider débranche vos administrateurs et vos clients d’un coup, sans rien changer aux paniers ni aux commandes. Enchaînez d’ailleurs en supprimant le contenu de var/cache, puis ouvrez une fenêtre privée trente secondes (pas plus), histoire d’éliminer un vieux cookie resté chez vous.