Chapitre 9.2 — Tokens, authentification et sécurité
Partie 9 : Web Services — Chapitre 2/5 Public : dev Next.js/React. On a l’architecture (9.1) ; ici, la sécurité et l’authentification. Version de référence : Moodle 5.2.
⏱️ TL;DR
- Un token = une chaîne opaque liée à un utilisateur + un service. Tout appel avec ce token s’exécute avec les permissions de cet utilisateur.
- Trois façons d’obtenir un token : (1) manuel (l’admin le crée dans l’interface), (2)
login/token.php(échanger identifiant/mot de passe contre un token, pour une app), (3) service mobile (flux dédié à l’app officielle).- Capabilities requises : l’utilisateur doit avoir
webservice/rest:use(utiliser REST) +webservice/*:createtokenpour s’auto-générer un token + les capabilities de chaque fonction appelée.- Compte de service (recommandé pour un backend) : un utilisateur dédié aux intégrations, avec un rôle système custom portant exactement les capabilities nécessaires. Jamais l’admin.
- Restrictions : token expirable, IP restrictions (limiter aux IP de votre serveur), service restreint aux utilisateurs autorisés, fonctions limitées au strict nécessaire.
- Règle d’or : un token est un secret d’identité. Il vit côté serveur uniquement (variables d’env, jamais dans un bundle client), transite en HTTPS, et se révoque en cas de doute.
🎯 Objectifs
- Obtenir un token par les trois méthodes et savoir laquelle choisir.
- Créer un compte de service à moindre privilège avec un rôle custom.
- Configurer les restrictions (IP, expiration, utilisateurs autorisés).
- Comprendre les capabilities de la chaîne web service.
- Appliquer les règles de protection du token.
1. Ce qu’est un token, exactement
Un token est une chaîne opaque (ex. a1b2c3…) stockée dans mdl_external_tokens, qui lie :
- un utilisateur (l’identité au nom de laquelle les appels s’exécutent),
- un service (l’ensemble des fonctions autorisées, ch. 9.1),
- des contraintes optionnelles (expiration, IP, validité).
Présenter le token à l’endpoint REST, c’est dire « je suis cet utilisateur, sur ce service ». Moodle exécute alors la fonction avec les capabilities de cet utilisateur (Partie 2). Le token ne confère aucun pouvoir supplémentaire : il incarne un utilisateur.
⚠️ Piège fondamental : un token = une identité complète. Qui possède le token peut agir comme l’utilisateur, dans la limite de ses permissions. Un token d’admin volé = compromission totale. Un token de service à moindre privilège volé = dégâts bornés aux capabilities de ce compte. C’est pourquoi le moindre privilège n’est pas optionnel (§4).
2. Les trois façons d’obtenir un token
2.1 Manuel (par l’admin)
Administration → Serveur → Web services → Gérer les tokens → « Créer un token » : on choisit l’utilisateur, le service, et éventuellement une date d’expiration et des IP autorisées. Le token s’affiche. Idéal pour un backend (un compte de service, un token, copié dans les variables d’environnement).
2.2 login/token.php (échange identifiant → token)
Une app peut obtenir un token programmatiquement en échangeant des identifiants :
curl 'https://moodle.example.com/login/token.php' \
--data-urlencode 'username=alice' \
--data-urlencode 'password=secret' \
--data-urlencode 'service=integration_nextjs'
# → {"token":"a1b2c3...","privatetoken":"..."}Le paramètre service est le shortname du service. La réponse contient le token. Usage typique : une app où chaque utilisateur se connecte avec ses identifiants et obtient son token (ex. l’app mobile). Nécessite que le service soit activé et que l’utilisateur ait le droit de créer un token (webservice/rest:use + capability de création).
⚠️ Piège :
login/token.phptransmet le mot de passe — donc HTTPS obligatoire, et jamais depuis un composant client public (le mot de passe transiterait par le navigateur). Cet échange se fait côté serveur (route handler Next.js), ou dans un contexte contrôlé.
2.3 Le service mobile
L’app officielle utilise un flux dédié (service moodle_mobile_app activable en un clic, souvent avec SSO/OAuth). Vous n’en avez pas besoin pour une intégration custom, mais sachez qu’il existe et qu’il est le chemin de l’app mobile.
3. La chaîne de capabilities
Pour qu’un appel réussisse, plusieurs capabilities doivent être réunies sur l’utilisateur du token :
webservice/rest:use— le droit d’utiliser le protocole REST. Sans elle, aucun appel REST ne passe.- La capability de création de token (
moodle/webservice:createtokenou équivalent) — seulement si l’utilisateur s’auto-génère un token vialogin/token.php. - Les capabilities propres à chaque fonction appelée — chaque external function déclare les capabilities qu’elle vérifie (ex.
core_enrol_get_users_coursesrequiert de pouvoir voir les inscriptions ; une fonction d’inscription requiertenrol/manual:enrol).
Autrement dit, activer un token ne suffit pas : l’utilisateur doit aussi avoir le droit métier de faire ce que la fonction fait. C’est le modèle capabilities de la Partie 2, appliqué aux web services.
⚠️ Piège fréquent : « mon token est valide mais l’appel échoue avec une erreur de permission ». C’est presque toujours la capability métier manquante (étape 3) : le compte de service a
webservice/rest:usemais pas le droit d’inscrire/lire ce que la fonction fait. Solution : donner à l’utilisateur (via un rôle) exactement les capabilities des fonctions qu’il doit appeler.
4. Le compte de service à moindre privilège
Pour un backend (Next.js, SIRH), la bonne pratique est un utilisateur dédié aux intégrations, avec un rôle système custom portant exactement les capabilities nécessaires.
Procédure :
- Créer un utilisateur dédié (ex.
svc_nextjs), non-humain, mot de passe fort, e-mail de service. - Créer un rôle système (Utilisateurs → Permissions → Définir les rôles) « Service Next.js » assignable au contexte système, avec :
webservice/rest:use- les capabilities métier des fonctions du service (ex.
moodle/course:view,enrol/manual:enrol…).
- Assigner ce rôle à
svc_nextjsau niveau système (Administration → Utilisateurs → Attribuer les rôles système). - Générer un token manuel pour
svc_nextjssur le service d’intégration. - Restreindre (IP, expiration, §5).
Résultat : si le token fuit, l’attaquant ne peut faire que ce que svc_nextjs peut faire — un périmètre que vous avez délibérément borné.
⚠️ Piège majeur : ne jamais utiliser le compte admin (ou un token d’admin) pour une intégration. Un token d’admin permet tout (créer des utilisateurs, changer des réglages, lire toutes les données). C’est l’erreur qui transforme une fuite de token en catastrophe. Compte dédié, capabilities minimales, toujours.
💡 Pour un dev React : c’est le principe des service accounts de vos plateformes cloud (un compte machine avec des rôles IAM précis), transposé au modèle capabilities de Moodle. Le rôle custom = votre policy IAM ; le token = la clé de service account. Vous ne donnez jamais les droits « owner » à un service qui n’a besoin que de « lire les cours ».
5. Les restrictions : réduire la surface
Un token peut (et devrait) être contraint :
- Expiration : une date de validité (
validuntil). Utile pour les tokens temporaires ; pour un backend permanent, on gère plutôt la rotation (régénérer périodiquement). - IP restrictions (
iprestriction) : limiter l’usage du token aux IP de votre serveur Next.js/backend. Un token volé et rejoué depuis une autre IP est refusé. Très efficace pour un backend à IP fixe. - Service restreint aux utilisateurs autorisés : sur le service, cocher « Utilisateurs autorisés seulement » et lister qui peut l’utiliser — double barrière.
- Fonctions minimales : n’ajouter au service que les fonctions réellement appelées (ch. 9.1, moindre privilège).
- HTTPS : imposé en production ; un token en clair sur HTTP est interceptable.
⚠️ Piège : un token sans IP restriction ni expiration, avec un service trop large, sur un compte trop puissant, exposé dans un repo git ou un bundle client = le scénario de compromission classique. Cumulez les restrictions ; chacune réduit l’impact d’une fuite.
6. Protéger et révoquer le token
- Stockage : côté serveur uniquement (variables d’environnement, gestionnaire de secrets). Jamais dans un composant client, un bundle JS, un repo public, un log.
- Transit : HTTPS systématique.
- Rotation : régénérez périodiquement (et immédiatement en cas de doute). L’ancien token est invalidé.
- Révocation : Gérer les tokens → supprimer le token compromis. L’accès est coupé instantanément.
- Audit : Moodle journalise les appels de web services (logs) — surveillez les usages anormaux (volumes, IP, fonctions inhabituelles).
⚠️ Piège Next.js spécifique : ne mettez jamais le token dans une variable d’environnement préfixée
NEXT_PUBLIC_— elle serait inlinée dans le bundle client et donc publique. Le token va dans une variable serveur (sansNEXT_PUBLIC_), lue uniquement dans des route handlers / server actions (détaillé au ch. 9.5).
7. Synthèse
La sécurité des web services tient en trois idées : un token incarne un utilisateur (donc compte de service à moindre privilège, jamais admin) ; la chaîne de capabilities doit être complète (webservice/rest:use + fonctions) ; les restrictions (IP, expiration, HTTPS, service minimal) et la protection du secret (serveur uniquement) bornent l’impact d’une fuite. Le reste de la partie s’appuie sur un tel token propre.
✏️ Exercices
Exercice 1 — Choisir la méthode d’obtention.
Pour chaque cas, token manuel / login/token.php / service mobile ? (a) backend Next.js unique qui synchronise des cours ; (b) app web où chaque prof gère ses cours avec son compte ; (c) déploiement de l’app mobile officielle.
✅ Solution
(a) Token manuel pour un compte de service dédié (un token dans les variables d’env du backend) ; (b) login/token.php — chaque prof échange ses identifiants (côté serveur) contre son token, agissant en son nom ; (c) service mobile dédié (flux de l’app officielle). Le critère : « au nom de qui » et « un seul compte de service ou un token par utilisateur ».
Exercice 2 — Token valide, appel refusé.
Un compte de service a webservice/rest:use et un token valide, mais core_enrol_get_users_courses échoue avec une erreur de permission. Pourquoi ?
✅ Solution
Il manque les capabilities métier de la fonction (étape 3 de la chaîne) : webservice/rest:use autorise à utiliser REST, mais pas à voir les inscriptions/cours ciblés. Il faut donner au compte de service, via son rôle custom, les capabilities requises par la fonction (ex. voir les cours de l’utilisateur ciblé). Activer le token ne remplace jamais les permissions métier.
Exercice 3 — Compte admin en service.
Un dev configure l’intégration avec un token du compte admin « parce que ça marche tout de suite ». Quel est le risque et que recommandez-vous ?
✅ Solution
Risque : un token d’admin peut tout faire (créer/supprimer des utilisateurs, changer des réglages, lire toutes les données) ; s’il fuit (repo, log, bundle, interception), c’est la compromission totale de la plateforme. Recommandation : créer un utilisateur de service dédié avec un rôle système custom portant exactement les capabilities des fonctions utilisées + webservice/rest:use, générer le token pour lui, et appliquer les restrictions (IP, HTTPS). Moindre privilège : une fuite ne coûte alors que le périmètre borné du service.
Exercice 4 — Restreindre un token de backend. Votre backend a une IP fixe. Quelles restrictions appliquez-vous au token, et laquelle est la plus efficace contre le rejeu ?
✅ Solution
Appliquer : IP restriction (= l’IP du backend), HTTPS, service restreint aux utilisateurs autorisés et fonctions minimales, éventuellement une rotation régulière. La plus efficace contre le rejeu d’un token volé est l’IP restriction : même avec le token, un attaquant sur une autre IP est refusé. (À combiner avec un stockage serveur strict pour éviter la fuite en premier lieu.)
Exercice 5 — Fuite Next.js.
Un dev met NEXT_PUBLIC_MOODLE_TOKEN dans son .env. Pourquoi est-ce grave, et comment corriger ?
✅ Solution
Le préfixe NEXT_PUBLIC_ injecte la variable dans le bundle client : le token devient visible par n’importe quel visiteur (DevTools, source du JS) → fuite publique d’une identité et de ses permissions. Correctif : renommer en variable serveur (MOODLE_WS_TOKEN, sans NEXT_PUBLIC_), ne l’utiliser que dans des route handlers / server actions, et révoquer + régénérer le token déjà exposé. Le token ne doit jamais atteindre le navigateur (ch. 9.5).
🧠 Quiz de révision
Q1. Que représente un token et quel est le principe de sécurité central ?
Réponse
Q2. Quelles sont les trois façons d’obtenir un token et leurs usages ?
Réponse
login/token.php (échange identifiant/mot de passe → token par utilisateur, pour une app), service mobile (flux de l’app officielle).Q3. Quelles capabilities forment la chaîne d’un appel web service ?
Réponse
webservice/rest:use (utiliser REST), la capability de création de token si auto-génération (login/token.php), et surtout les capabilities métier de chaque fonction appelée. Le token seul ne suffit pas : il faut le droit métier.Q4. Comment configure-t-on un compte de service à moindre privilège ?
Réponse
webservice/rest:use + exactement les capabilities des fonctions utilisées, l’attribuer au contexte système, générer un token manuel pour lui, et appliquer les restrictions. Jamais le compte admin.Q5. Citez trois restrictions/pratiques qui bornent l’impact d’une fuite de token.
Réponse
NEXT_PUBLIC_ ni bundle client), révocation immédiate en cas de doute.Chapitre suivant : 03 — Les fonctions externes du core — le catalogue des fonctions (core_course_*, core_enrol_*, core_user_*, gradereport_*…), lire leur signature dans la doc API intégrée, et les appeler en REST (curl + TypeScript) : lister des cours, inscrire un utilisateur, lire des notes.