Aller au contenu

#Authentification

Deux façons de se présenter, une seule façon de désigner le site sur lequel on travaille, et deux portées qui décident de ce qu’on a le droit d’y faire. Cette page dit exactement ce que chaque en-tête affirme — et ce qu’il n’affirme pas, ce qui est souvent plus important.

#Les deux modes

ModeQui l’utiliseCe qu’il envoie
SessionLe navigateur, sur le CMSLes cookies Supabase, posés à la connexion
Clé d’APILe CLI, un CI, un scriptAuthorization: Bearer wc_…

L’en-tête gagne quand il est là. Une requête qui porte une clé ET un cookie de session est traitée comme celle du porteur de la clé, jamais comme celle de la session. Préférer silencieusement le cookie donnerait des droits que l’appelant n’a pas demandés, et rendrait incompréhensible le débogage d’une clé révoquée qui « marche encore » depuis un navigateur connecté.

Un jeton Bearer qui ne commence pas par wc_ — un JWT Supabase, typiquement — est ignoré : la requête retombe sur le chemin de session au lieu d’être refusée.

#La forme d’une clé

Une clé d’API
wc_kJ8pQx3nR7vL2mZ5tY9wB4cF6hN1sD0aG8eU3iO7pK

Le préfixe wc_ suivi de 32 octets tirés au hasard, encodés en base64url — 43 caractères, sans remplissage. Base64url et non base64 parce que la clé voyage dans un en-tête HTTP, une variable d’environnement et parfois une URL, où +, / et = se font échapper ou tronquer.

Danger

La base ne conserve que le SHA-256 du secret et ses douze premiers caractères. Le secret n’est affiché qu’une fois, à la création, et il n’existe nulle part où aller le rechercher — ni chez toi, ni chez nous. Une clé perdue se révoque et se remplace, elle ne se retrouve pas.

C’est la seule propriété qui rende une fuite de la base survivable côté authentification : une copie de sauvegarde ne permet de se faire passer pour personne.

Le mot Bearer est comparé sans tenir compte de la casse — bearer, Bearer et BEARER conviennent, la RFC 7235 le veut ainsi.

#L’en-tête qui désigne le site

Un compte peut avoir plusieurs sites. Une clé les atteint tous, puisqu’elle appartient au compte et non au site. Reste à dire lequel on vise :

L’en-tête de désignation
X-Webcosa-Site-Cible: atelier-durand

Il accepte le slug ou l'uuid du site. Le slug, parce qu’un humain qui écrit un script connaît le slug de son site et pas son uuid, et que l’obliger à aller le chercher dans l’interface est une friction gratuite.

Attention

Le suffixe -Cible n’est pas décoratif, et le raccourcir casse tout en silence.

X-Webcosa-Site — sans suffixe — est un en-tête que le proxy purge sans condition de toute requête entrante, puis repose lui-même depuis l’hôte réel. Un client qui l’enverrait verrait sa valeur disparaître avant d’atteindre la route : le site demandé serait null, on retomberait sur le premier site du compte, et une écriture de thème réussirait sur le mauvais site sans le moindre message. Le mode d’échec le plus coûteux qui soit — une commande qui réussit au mauvais endroit.

X-Webcosa-Site-Cible traverse, parce qu’il n’affirme rien : il demande.

#Ce qui se passe quand il est absent

Le site retenu est le premier accessible au porteur, dans l’ordre où la base les rend : par nom, croissant. Autrement dit, alphabétique.

C’est un défaut commode et instable à la fois. Il fonctionne parfaitement tant que le compte n’a qu’un site ; il se met à désigner quelqu’un d’autre le jour où un site dont le nom commence par « A » rejoint le compte. Deux garde-fous :

  1. Le champ site de la réponse de GET /api/themes dit toujours quel site a été résolu. Le CLI l’inscrit dans son .webcosa.json pour cette raison.
  2. Un en-tête fourni mais introuvable n’entraîne aucun repli : la réponse est 403 site_interdit. Servir le site B à qui demande le site A serait la pire des complaisances.

#Désigner n’est pas autoriser

L’en-tête est revalidé à chaque requête contre les sites accessibles au compte porteur de la clé, pas contre une liste figée à la création. Retirer un site à une agence retire du même coup l’accès de toutes ses clés, sans avoir à les révoquer une par une.

  1. Le secret est haché et confronté à la base

    En une seule requête, qui vérifie l’empreinte, rejette les clés révoquées ou périmées, et horodate derniere_utilisation. Trois cas — inconnue, révoquée, périmée — donnent la même réponse : 401 unauthorized. Les distinguer renseignerait quelqu’un qui teste des clés volées.

  2. La portée est confrontée à celle qu’exige la route

    Insuffisante, c’est 403 portee_insuffisante, avant toute lecture de données.

  3. Le site demandé est confronté aux sites du compte

    Absent de la liste, c’est 403 site_interdit. Compte sans aucun site, c’est 409 no_site.

#Les deux portées

Volontairement grossières. Une matrice de permissions fine que personne ne comprend produit des clés « tout cocher » — donc moins de sécurité réelle qu’un choix binaire qu’on fait correctement.

lectureportée

Lire les thèmes, leur arborescence, le contenu d’un fichier, et exporter un thème entier. Rien d’autre. C’est la portée d’une sauvegarde nocturne ou d’un tableau de bord : elle ne peut pas casser un site, même mal utilisée.

themesportéedéfaut : à la création d’une clé

Tout ce que permet lecture, plus les écritures : installer, dupliquer, renommer, publier, retirer un thème, écrire et supprimer des fichiers, importer une archive. C’est la portée du CLI.

themes couvre lecture ; l’inverse est faux. Une route qui n’exige rien explicitement exige lecture — le défaut le plus permissif serait ici le mauvais choix.

#Portées et opérations

Opérationlecturethemes
GET /api/themesouioui
POST /api/themesnonoui
PATCH /api/themesnonoui
DELETE /api/themesnonoui
GET /api/themes/<id>/fichiersouioui
PUT /api/themes/<id>/fichiersnonoui
DELETE /api/themes/<id>/fichiersnonoui
GET /api/themes/<id>/exportouioui
POST /api/themes/importernonoui
GET, POST, DELETE sur /api/clesnonnon

La dernière ligne n’est pas une coquille : aucune portée n’ouvre /api/cles. Seule une session de navigateur y donne accès. Voir Clés.

#Ce que le proxy laisse passer, et ce qu’il arrête

Une garde d’authentification s’exécute en bordure, avant les routes. Elle ne vérifie pas les clés — elle n’en a pas les moyens, et surtout elle ne doit pas : une identité résolue en bordure devrait voyager jusqu’à la route par un en-tête, c’est-à-dire exactement le mécanisme qui a déjà produit une faille dans ce produit.

Elle se contente donc de ne pas refuser, et seulement sur les routes nommées :

Les seules routes ouvertes aux clés
/api/themes
/api/themes/<id>/fichiers
/api/themes/<id>/export
/api/themes/importer

Sur tout autre chemin, une requête sans cookie reçoit 401 unauthorized du proxy, quelle que soit la validité de la clé présentée. C’est ce qui garantit qu’une route ajoutée demain n’est pas publique par accident.

#Un exemple complet

curl -s -X PATCH https://cms.webcosa.com/api/themes \-H "Authorization: Bearer $WEBCOSA_CLE" \-H "X-Webcosa-Site-Cible: atelier-durand" \-H "Content-Type: application/json" \-d '{"id":"22222222-2222-4222-8222-222222222222","nom":"Atelier v2"}'{"ok":true}
Astuce

En intégration continue, une clé de portée themes dédiée et datée. Une clé de CI est celle qui traîne le plus longtemps dans le plus d’endroits ; elle est aussi la seule dont l’expiration ne surprendra personne, puisqu’un CI ne travaille pas à trois heures du matin sur un déploiement urgent.