Chapitre 10.1 — Comprendre LTI : le standard qui connecte les outils aux LMS
Vous savez construire des applications web. Vous maîtrisez Next.js, React, TypeScript, les API REST, OAuth 2.0 et les JWT. Et pourtant, le jour où un client vous demande « intégrez votre application dans notre Moodle », vous découvrez un monde parallèle avec son propre vocabulaire, ses propres flux d’authentification, et un acronyme qui revient partout : LTI, pour Learning Tools Interoperability.
La bonne nouvelle, c’est que LTI n’est pas un OVNI. C’est un standard construit sur des briques que vous connaissez déjà — OpenID Connect, JWT signés, OAuth 2.0 — auxquelles s’ajoutent des claims métier normalisés pour le monde éducatif : qui est l’utilisateur, dans quel cours il se trouve, quel est son rôle, où renvoyer sa note. Une fois que vous avez compris cette architecture, intégrer n’importe quel outil dans n’importe quel LMS (Moodle, Canvas, Blackboard, Brightspace, Sakai…) devient un exercice répétable.
Ce chapitre est le socle conceptuel de toute la Partie 10. Nous n’écrirons presque pas de code ici : l’objectif est que vous compreniez pourquoi LTI existe, quel problème il résout, comment il a évolué (et pourquoi certaines versions sont mortes), et où Moodle 5.2 se situe dans ce paysage. Les chapitres suivants plongeront dans la mécanique cryptographique, la configuration côté Moodle, les services LTI Advantage, et la construction complète de notre outil fil rouge : QuizLab, une application Next.js hébergée sur https://tool.example.com que nous brancherons sur notre plateforme https://moodle.example.com.
Mini-sommaire du chapitre :
- Le problème que LTI résout — le monde d’avant, et ce que le standard apporte.
- Le vocabulaire LTI, précisément — platform, tool, deployment, resource link, context… la section de référence à garder sous le coude.
- Histoire des versions — de LTI 1.0 (2010) à LTI 1.3 (2019), en passant par le cadavre de LTI 2.0.
- LTI Advantage — AGS, NRPS, Deep Linking 2.0, et la certification 1EdTech.
- L’état des lieux dans Moodle 5.2 — mod_lti, enrol_lti, le legacy déprécié, la dynamic registration.
- Cas d’usage réels — H5P.com, Turnitin, labs virtuels, et notre QuizLab.
- Panorama de la Partie 10 — la feuille de route des chapitres 02 à 06.
1. Le problème que LTI résout
1.1 Le monde d’avant : l’enfer des intégrations ad hoc
Remontons vers 2008. Les LMS (Learning Management Systems) sont déjà bien installés dans les universités : Moodle, Blackboard, Sakai, Desire2Learn. Parallèlement, une industrie entière d’outils pédagogiques spécialisés explose : simulateurs de chimie, plateformes d’exercices de mathématiques, détecteurs de plagiat, éditeurs de contenus interactifs. Le problème : ces deux mondes ne se parlent pas.
Concrètement, si vous étiez l’éditeur d’un outil d’exercices en ligne et que vous vouliez qu’un enseignant l’utilise depuis son cours Moodle, vous aviez trois options, toutes mauvaises :
Option 1 : le lien brut. L’enseignant colle une URL dans son cours. L’étudiant clique, atterrit sur votre site… et se retrouve devant un écran de connexion. Il doit créer un compte, se souvenir d’un deuxième mot de passe, et vous, éditeur, vous devez gérer un annuaire d’utilisateurs qui duplique celui du LMS. L’enseignant, lui, doit apparier manuellement « jdupont42 sur votre site » avec « Jean Dupont dans son cours Moodle ». À l’échelle d’un cours de 300 étudiants, c’est intenable.
Option 2 : l’iframe sans identité. Vous embarquez votre outil dans une iframe à l’intérieur du cours. Visuellement c’est intégré, mais techniquement l’iframe ne sait rien : elle ne sait pas qui la regarde, depuis quel cours, avec quel rôle. Impossible de personnaliser l’expérience, impossible de sauvegarder la progression d’un étudiant précis, impossible de renvoyer une note. C’est une affiche collée sur un mur, pas une intégration.
Option 3 : l’intégration sur mesure. Vous développez un connecteur spécifique pour Moodle (un plugin PHP), un autre pour Blackboard (des Building Blocks en Java), un autre pour Canvas, un autre pour Sakai… Chaque connecteur a son propre modèle d’authentification, son propre cycle de release, ses propres bugs. Vous ne vendez plus un produit, vous maintenez N produits. Et chaque université qui utilise une version différente du LMS vous demande une variante. Les coûts explosent, l’innovation ralentit.
Et pour les notes ? Dans les trois cas, le scénario le plus courant était d’une trivialité désespérante : l’enseignant exportait un CSV depuis l’outil externe, puis recopiait les notes à la main dans le carnet de notes du LMS. Ligne par ligne. Avec les erreurs de saisie que cela implique, et l’impossibilité pour l’étudiant de voir sa note au même endroit que le reste de son cours.
C’est ce triple problème — identité dupliquée, absence de contexte, notes recopiées manuellement — que le consortium IMS Global (aujourd’hui 1EdTech, nous y reviendrons) a entrepris de résoudre en 2010 avec la première version de LTI.
1.2 Ce que LTI apporte
LTI définit un contrat standardisé entre deux acteurs : le LMS (appelé platform) et l’application externe (appelée tool). Ce contrat couvre quatre besoins fondamentaux :
Le SSO transparent. Quand un étudiant clique sur une activité LTI dans son cours Moodle, il arrive dans l’outil externe déjà authentifié. Pas d’écran de connexion, pas de deuxième compte, pas de mot de passe supplémentaire. Le LMS affirme cryptographiquement à l’outil : « cet utilisateur est untel, je m’en porte garant ». C’est de la fédération d’identité, exactement comme un « Se connecter avec Google », sauf que le fournisseur d’identité est le LMS.
Le contexte. L’outil ne reçoit pas seulement une identité, il reçoit tout le contexte pédagogique : qui est l’utilisateur (identifiant stable, nom, email si autorisé), quel cours il consulte (le context), quel lien précis il a cliqué (le resource link), et surtout quel rôle il a — étudiant, enseignant, administrateur. Ce dernier point est décisif : votre outil peut afficher l’interface « répondre au quiz » à l’étudiant et l’interface « éditer le quiz et voir les statistiques » à l’enseignant, à partir du même lien, sans aucune configuration.
Le retour de notes. L’outil peut renvoyer les résultats directement dans le carnet de notes du LMS, via une API standardisée. La note obtenue dans QuizLab apparaît dans le carnet de notes Moodle comme n’importe quelle autre note, automatiquement. Fini le CSV recopié à la main.
L’interopérabilité inter-LMS. C’est le point qui justifie le « I » de LTI. Un outil qui implémente correctement le standard fonctionne avec tous les LMS certifiés : Moodle, Canvas, Blackboard Learn, Brightspace (D2L), Sakai, Schoology, et des dizaines d’autres. Vous développez une seule intégration, vous adressez tout le marché de l’éducation. Pour un éditeur d’outil, c’est la différence entre un produit viable et un gouffre de maintenance.
💡 Pour un dev React : l’analogie la plus utile à garder en tête est celle-ci : LTI 1.3 ≈ un OpenID Connect spécialisé éducation. Si vous avez déjà implémenté « Sign in with Google » avec NextAuth ou une lib OIDC, vous connaissez 70 % de la mécanique : redirection vers un serveur d’autorisation, réception d’un
id_tokenJWT signé, vérification de signature via un endpoint JWKS. LTI 1.3 ajoute par-dessus : (1) un flux de login initié par un tiers (le LMS, pas l’utilisateur), (2) des claims métier normalisés (context,roles,resource_link…), et (3) des API de services (notes, roster) protégées par OAuth 2.0client_credentials. Rien d’exotique — juste des conventions supplémentaires sur des standards que vous pratiquez déjà.
1.3 Le lancement LTI en un coup d’œil
Avant de rentrer dans le vocabulaire et l’histoire, fixons l’image mentale du scénario nominal, celui que nous décortiquerons octet par octet au chapitre 10.2 :
Étudiant (navigateur) Moodle (platform) QuizLab (tool)
https://moodle.example.com https://tool.example.com
│ │ │
│ 1. Clique sur l'activité │ │
│ "Quiz chapitre 3" │ │
│─────────────────────────────>│ │
│ │ │
│ 2. Redirection vers le │ │
│ login initiation de │ │
│ l'outil (avec iss, │ │
│ login_hint, etc.) │ │
│<─────────────────────────────│ │
│ │
│ 3. GET/POST /api/lti/login │
│────────────────────────────────────────────────────────────>│
│ │
│ 4. Redirection vers l'authorization endpoint de Moodle │
│ (/mod/lti/auth.php) avec state + nonce │
│<────────────────────────────────────────────────────────────│
│ │ │
│ 5. GET /mod/lti/auth.php │ │
│─────────────────────────────>│ │
│ │ Moodle vérifie la session, │
│ │ construit et signe un JWT │
│ 6. Auto-POST (form) du │ (id_token) en RS256 │
│ id_token vers l'outil │ │
│<─────────────────────────────│ │
│ │
│ 7. POST /api/lti/launch (id_token + state) │
│────────────────────────────────────────────────────────────>│
│ │
│ │ 8. L'outil récupère la clé │
│ │ publique de Moodle via │
│ │ /mod/lti/certs.php, │
│ │<─── vérifie la signature ────│
│ │
│ 9. L'outil crée sa session et affiche le quiz, │
│ personnalisé selon l'utilisateur, le cours et le rôle │
│<────────────────────────────────────────────────────────────│
│ │Retenez la forme générale : tout transite par le navigateur de l’utilisateur (redirections et auto-POST de formulaires), et la confiance repose sur un JWT signé par la clé privée de la plateforme, que l’outil vérifie avec la clé publique exposée en JWKS. Aucun secret partagé ne circule au moment du lancement. Nous verrons au chapitre 10.2 pourquoi ce flux est « third-party initiated » et ce que cela change par rapport à un OIDC classique.
⚠️ Piège : ne confondez pas ce flux de lancement (qui authentifie un utilisateur et transporte le contexte) avec les appels de services (récupérer le roster, pousser une note). Les seconds sont des appels serveur-à-serveur, sans navigateur, protégés par un access token OAuth 2.0 obtenu en
client_credentialssur/mod/lti/token.php. Beaucoup de développeurs débutants en LTI essaient de pousser des notes « depuis le launch » ou stockent le JWT de lancement pour appeler les API plus tard — c’est une erreur d’architecture. Les deux canaux sont indépendants et utilisent des mécanismes de sécurité différents.
2. Le vocabulaire LTI, précisément
LTI a un vocabulaire spécifique, et l’utiliser correctement n’est pas du purisme : les termes correspondent à des objets distincts dans le protocole, avec des identifiants distincts, et les confondre produit des bugs bien réels (une note poussée dans la mauvaise colonne, un tenant mal isolé, une configuration qui casse au deuxième déploiement). Cette section est votre référence — revenez-y chaque fois qu’un terme vous échappe dans les chapitres suivants.
2.1 Tableau de référence
| Terme | Définition | Exemple dans notre fil rouge | Ancien nom (LTI 1.1) |
|---|---|---|---|
| Platform | Le LMS qui héberge les cours et lance les outils. C’est lui qui détient les comptes utilisateurs et signe les jetons de lancement. | Moodle sur https://moodle.example.com | Tool Consumer |
| Tool | L’application externe lancée depuis la plateforme. Elle consomme l’identité et le contexte fournis. | QuizLab (Next.js) sur https://tool.example.com | Tool Provider |
Issuer (iss) | L’identifiant unique de la plateforme, sous forme d’URL. C’est la valeur du claim iss dans les JWT. | https://moodle.example.com | — (n’existait pas) |
| Client ID | L’identifiant attribué par la plateforme à l’outil lors de son enregistrement, au sens OAuth 2.0. | a1b2c3d4e5f6g7h | Consumer key (approximativement) |
Deployment / deployment_id | Une « installation » de l’outil au sein de la plateforme. Un même client_id peut couvrir plusieurs déploiements. | 1 | — (n’existait pas) |
| Context | Le conteneur pédagogique depuis lequel le lancement a lieu — dans Moodle, un cours. | Le cours « Chimie L1 », claim context.id | Context (déjà) |
| Resource link | Le lien précis posé dans le cours, l’instance d’activité que l’utilisateur a cliquée. Un cours peut contenir plusieurs resource links vers le même outil. | L’activité « Quiz chapitre 3 », claim resource_link.id | resource_link_id |
| Message | Une communication via le navigateur (redirections, POST de JWT) : lancement de ressource, deep linking… | Le LtiResourceLinkRequest du schéma ci-dessus | Launch request |
| Service | Une communication serveur-à-serveur (API REST + OAuth 2.0) : notes, roster… | QuizLab appelle AGS pour pousser une note | Basic Outcomes (POX) |
| Launch | Le processus complet par lequel la plateforme lance l’outil pour un utilisateur donné, aboutissant à la remise d’un message signé. | Les étapes 1 à 9 du diagramme précédent | Launch (déjà) |
2.2 La bascule terminologique : consumer/provider → platform/tool
Dans les spécifications LTI 1.0 et 1.1, le LMS s’appelait Tool Consumer (il « consomme » l’outil) et l’application externe Tool Provider (elle « fournit » l’outil). LTI 1.3 a renversé ce vocabulaire : le LMS devient la platform et l’application externe le tool.
Ce n’est pas qu’un caprice de nommage. La terminologie 1.1 était source de confusion permanente : « consumer » et « provider » ont un sens précis en OAuth (le consumer signe les requêtes, le service provider les vérifie) qui entrait en collision avec l’intuition. Et surtout, avec LTI 1.3, le LMS joue le rôle d’OpenID Provider au sens OIDC (c’est lui qui émet les id_token), pendant que l’outil joue le rôle de Relying Party. Garder « Tool Provider » pour désigner celui qui, au sens OIDC, est le consommateur de jetons aurait été intenable.
Retenez la correspondance, car vous croiserez les deux vocabulaires : le code de Moodle, ses tables SQL (lti_types, lti_tool_consumer_map…) et sa documentation historique regorgent encore de « consumer » hérités de l’ère 1.1.
⚠️ Piège : dans les paramètres d’un lancement LTI 1.1, vous trouverez
tool_consumer_instance_guid,oauth_consumer_key, etc. Dans unid_tokenLTI 1.3, ces notions sont remplacées par le tripletiss/aud(le client_id) /deployment_idet par le claimtool_platform. Si vous lisez un tutoriel qui mélangeoauth_consumer_keyetid_token, il mélange deux générations du protocole — méfiez-vous de tout le reste de son contenu.
2.3 Le triplet (issuer, client_id, deployment_id) : l’identité d’un tenant
C’est le concept le plus important de cette section, et celui que les développeurs venant du web « classique » sous-estiment systématiquement. Quand votre outil reçoit un lancement, il doit répondre à la question : « de quel client s’agit-il ? » — au sens SaaS du terme : quelle organisation, quel espace de données, quelle configuration. En LTI 1.3, la réponse est un triplet :
-
iss(issuer) : identifie la plateforme. Deux instances Moodle distinctes (l’université A et l’université B) ont deux issuers différents. Dans notre fil rouge :https://moodle.example.com. -
client_id(transporté dans le claimauddu JWT) : identifie l’enregistrement de votre outil sur cette plateforme. Une même plateforme peut enregistrer votre outil plusieurs fois (par exemple un enregistrement « production » et un enregistrement « bac à sable », ou deux contrats commerciaux distincts), et chaque enregistrement a son propre client_id et sa propre paire de clés côté outil. Dans notre fil rouge :a1b2c3d4e5f6g7h. -
deployment_id(claimhttps://purl.imsglobal.org/spec/lti/claim/deployment_id) : identifie un déploiement de cet enregistrement au sein de la plateforme. Dans notre fil rouge :"1".
Pourquoi ce troisième niveau, le deployment, existe-t-il ? Parce que dans les gros LMS multi-tenants, un même enregistrement d’outil (un client_id, une configuration de sécurité, une paire de clés) peut être « déployé » dans plusieurs sous-ensembles de la plateforme, chacun devant être facturé, configuré ou isolé séparément. Quelques scénarios concrets :
- Un district scolaire utilise une seule instance de LMS pour 40 établissements. L’outil est enregistré une fois au niveau du district (un client_id), mais activé établissement par établissement : chaque établissement reçoit son deployment_id. L’éditeur de l’outil peut ainsi savoir quel établissement a souscrit, activer les fonctionnalités payées par celui-ci, et produire une facturation par déploiement — sans exiger 40 enregistrements avec 40 échanges de clés.
- Une université enregistre l’outil globalement, mais la faculté de médecine et la faculté de droit ont des contrats de licence différents : deux déploiements, deux deployment_id, un seul client_id.
- Moodle, plus modestement, crée en pratique un deployment par « type d’outil » configuré. Dans une installation Moodle simple, vous verrez presque toujours
deployment_id: "1"— mais votre code ne doit jamais le supposer.
La règle d’or côté outil : le tenant est identifié par le triplet complet (iss, client_id, deployment_id), et les données utilisateur/cours doivent être cloisonnées par ce triplet. Un user.id LTI (le claim sub) n’est unique que relativement à son issuer : deux plateformes différentes peuvent émettre le même sub pour deux personnes différentes.
⚠️ Piège : indexer vos utilisateurs par le seul claim
subest la faille classique des outils LTI mal conçus. Si l’université A émetsub: "42"pour Alice et que l’université B émetsub: "42"pour Bob, un outil qui indexe parsubseul servira les données d’Alice à Bob. La clé primaire correcte pour un utilisateur côté outil est au minimum le couple(iss, sub)— et pour les données pédagogiques, ajoutez le deployment_id. C’est exactement le même problème que le claimsuben OIDC multi-IdP, mais dans un contexte où la fuite est une violation de données d’étudiants, avec les conséquences RGPD/FERPA qui vont avec.
2.4 Resource link vs context : deux granularités à ne pas confondre
Deuxième confusion fréquente. Le context est le cours ; le resource link est un lien précis posé dans ce cours. Un enseignant peut créer trois activités « Outil externe » pointant toutes vers QuizLab dans le même cours : « Quiz chapitre 1 », « Quiz chapitre 2 », « Quiz chapitre 3 ». Ces trois activités partagent le même context.id mais ont trois resource_link.id différents.
Cette distinction structure toute la conception de votre outil :
- La progression d’un étudiant sur un quiz donné se rattache au resource link (c’est ce quiz-là, dans ce cours-là).
- La configuration au niveau du cours (thème, langue, options d’équipe pédagogique) se rattache au context.
- Les colonnes de notes (nous verrons AGS plus loin) sont généralement liées à un resource link : la note du « Quiz chapitre 3 » va dans la colonne du « Quiz chapitre 3 ».
💡 Pour un dev React : pensez au resource link comme à une instance de votre composant et au context comme à son parent. Si QuizLab était une app React, le mapping mental serait :
<Course id={context.id}> <QuizInstance id={resource_link.id} quizId={...} /> ... </Course>. Le lancement LTI vous livre à chaque fois les props de l’instance cliquée. Et comme en React, l’état que vous stockez doit être rattaché au bon niveau de l’arbre : stocker la progression au niveau du context alors qu’elle dépend du resource link, c’est le bug du « state remonté trop haut » — tous les quiz du cours partageraient la même progression.
2.5 Message vs service : les deux canaux
Dernier couple à graver dans le marbre. LTI 1.3 distingue formellement :
-
Les messages : des échanges qui passent par le navigateur de l’utilisateur, sous forme de redirections HTTP et d’auto-POST de formulaires transportant des JWT. Le lancement de ressource (
LtiResourceLinkRequest) et le deep linking (LtiDeepLinkingRequest/LtiDeepLinkingResponse) sont des messages. Un message a toujours un utilisateur « au bout », avec une session navigateur active des deux côtés. -
Les services : des API REST appelées de serveur à serveur, sans navigateur, authentifiées par un access token OAuth 2.0 obtenu via le grant
client_credentials(avec une assertion JWT signée par l’outil — nous détaillerons ce mécanisme, ditclient_credentialsavecclient_assertion, au chapitre 10.2). AGS et NRPS sont des services. Un service peut être appelé à 3 h du matin par un cron de votre outil, sans qu’aucun utilisateur ne soit connecté.
Cette séparation est une force de conception : elle permet par exemple à QuizLab de pousser les notes d’un quiz après sa fermeture, en batch, ou de synchroniser le roster chaque nuit — des choses impossibles avec une architecture où tout dépendrait de la session de l’utilisateur.
3. Histoire des versions : de OAuth 1.0a à OpenID Connect
Comprendre l’histoire de LTI n’est pas un exercice de culture générale. D’abord parce que Moodle 5.2 embarque encore le code des versions anciennes (en mode déprécié), et que vous croiserez des installations où des outils 1.1 tournent toujours. Ensuite parce que les défauts des anciennes versions expliquent chaque choix de conception de LTI 1.3 : on ne comprend vraiment un standard qu’en connaissant les cicatrices qu’il recouvre.
La chronologie d’ensemble :
2010 2011 2014 2019 2021-2022
│ │ │ │ │
LTI 1.0 LTI 1.1 LTI 1.2 / 2.0 LTI 1.3 Dépréciation
(launch (+ Basic (2.0 : refonte (OIDC + JWT officielle de
OAuth 1.0a) Outcomes) "tool proxy", + OAuth 2.0) LTI 1.1 par
mort-né, + LTI Advantage 1EdTech (fin de
retiré) (AGS, NRPS, certification
Deep Linking 2.0) 2021, retrait
effectif mi-2022)Une précision sur l’organisme de standardisation : LTI est publié par 1EdTech, qui est le nouveau nom d’IMS Global Learning Consortium depuis son renommage en mai 2022. Les spécifications historiques portent donc la marque « IMS Global », les récentes « 1EdTech », mais c’est la même organisation — celle qui édite aussi QTI (format de questions), Common Cartridge, Caliper Analytics et OneRoster.
3.1 LTI 1.0 (2010) et 1.1 (2011) : le launch signé OAuth 1.0a
LTI 1.0 pose le principe fondateur : le lancement est un POST de formulaire HTML, envoyé par le navigateur de l’utilisateur vers l’URL de l’outil, contenant des paramètres en clair (user_id, roles, context_id, resource_link_id, lis_person_name_full…) et signé selon OAuth 1.0a.
Comment fonctionne cette signature ? La plateforme et l’outil partagent au préalable un couple consumer key / shared secret — typiquement échangé par email ou copié-collé dans un formulaire d’administration. Au moment du lancement :
- La plateforme rassemble tous les paramètres du POST, y ajoute les paramètres OAuth (
oauth_consumer_key,oauth_nonce,oauth_timestamp,oauth_signature_method=HMAC-SHA1,oauth_version=1.0). - Elle construit la signature base string : la méthode HTTP, l’URL de destination et l’ensemble des paramètres triés alphabétiquement et encodés selon les règles (pointilleuses) de RFC 5849.
- Elle calcule un HMAC-SHA1 de cette chaîne avec le shared secret comme clé, et place le résultat dans
oauth_signature. - L’outil, à la réception, refait exactement le même calcul avec sa copie du secret et compare les signatures. Il vérifie aussi que le
oauth_timestampest récent et que leoauth_noncen’a pas déjà été vu (anti-rejeu).
Voici à quoi ressemblait un lancement LTI 1.1 (paramètres du POST, simplifiés) :
POST https://tool.example.com/lti/launch (LTI 1.1 — historique, déprécié)
Content-Type: application/x-www-form-urlencoded
lti_message_type=basic-lti-launch-request
lti_version=LTI-1p0
oauth_consumer_key=quizlab_key
oauth_signature_method=HMAC-SHA1
oauth_timestamp=1720400000
oauth_nonce=8f3a2b1c9d
oauth_version=1.0
oauth_signature=q2xW8Kb3n0FvR1sT5uYw7zAe9iCk=
resource_link_id=42
user_id=17
roles=Learner
context_id=course-101
context_title=Chimie L1
lis_person_name_full=Alice Martin
lis_person_contact_email_primary=alice@example.edu
lis_outcome_service_url=https://moodle.example.com/mod/lti/service.php
lis_result_sourcedid={"data":{"instanceid":"42","userid":"17",...}}C’était simple à implémenter, et c’est ce qui a fait le succès fulgurant de LTI 1.0/1.1 : en quelques années, tous les LMS majeurs l’ont adopté. Mais les faiblesses structurelles sont visibles à l’œil nu pour quiconque a une culture sécurité moderne :
- Secret symétrique partagé. Le même secret sert à signer et à vérifier. Il doit être stocké en clair exploitable des deux côtés (impossible de le hacher comme un mot de passe, puisqu’il faut le rejouer dans le calcul HMAC). Chaque copie est un point de compromission. Et le secret transite au moins une fois par un canal humain (email, ticket, copier-coller).
- Spoofing total si le secret fuit. Quiconque possède le secret peut forger un lancement pour n’importe quel utilisateur avec n’importe quel rôle.
roles=Instructor,user_id=1, et vous voilà enseignant, voire administrateur, dans l’outil. Une fuite de secret = une usurpation d’identité illimitée et indétectable. - Pas d’expiration native. Le secret n’expire jamais tant qu’un humain ne le fait pas tourner à la main — c’est-à-dire, en pratique, jamais. La fenêtre anti-rejeu repose sur le couple timestamp/nonce, dont la vérification côté outil (stocker les nonces vus, tolérance d’horloge) était fréquemment bâclée voire absente.
- Signature fragile. Les règles d’encodage et de tri de la base string OAuth 1.0a sont si pointilleuses (encodage des espaces, des caractères réservés, gestion des paramètres dupliqués, de l’URL avec ou sans port…) que les incompatibilités d’implémentation étaient une source inépuisable de « signature mismatch » impossibles à déboguer.
- Données personnelles en clair dans le POST. Nom, email, identifiants transitent en paramètres de formulaire, visibles dans tout ce qui logge les corps de requêtes.
LTI 1.1 (2011) a ajouté la brique qui manquait le plus : Basic Outcomes, le retour de note. Le mécanisme : le lancement transporte une URL de service (lis_outcome_service_url) et un identifiant opaque (lis_result_sourcedid) désignant « la cellule du carnet de notes » pour cet utilisateur sur ce lien. L’outil peut ensuite appeler ce service — toujours signé OAuth 1.0a, en mode body signing — avec un corps XML au format POX (Plain Old XML) :
<!-- Requête Basic Outcomes LTI 1.1 (replaceResult) — historique, déprécié -->
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>msg-001</imsx_messageIdentifier>
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody>
<replaceResultRequest>
<resultRecord>
<sourcedGUID>
<sourcedId>{"data":{"instanceid":"42","userid":"17"}}</sourcedId>
</sourcedGUID>
<result>
<resultScore>
<language>en</language>
<textString>0.85</textString>
</resultScore>
</result>
</resultRecord>
</replaceResultRequest>
</imsx_POXBody>
</imsx_POXEnvelopeRequest>Trois opérations seulement (replaceResult, readResult, deleteResult), une note unique normalisée entre 0 et 1, aucune notion de colonnes multiples, de commentaires structurés, de statut de progression. C’était mieux que le CSV recopié à la main, mais très loin d’un vrai service de carnet de notes.
📚 Aller plus loin : la spécification LTI 1.1 est toujours consultable sur le site de 1EdTech (
https://www.imsglobal.org/specs/ltiv1p1) — sa lecture est instructive précisément parce qu’on y voit tout ce que 1.3 a corrigé. Le document « LTI 1.1 Migration Guide » de 1EdTech (https://www.imsglobal.org/spec/lti/v1p3/migr) explique comment les plateformes ont fait cohabiter les deux générations, y compris le mécanisme de migration des identifiants utilisateurs (claimlti1p1).
3.2 LTI 2.0 (2014) : l’ambition qui a tué
En 2014, IMS Global publie LTI 1.2 (une révision mineure de 1.1) et surtout LTI 2.0, une refonte ambitieuse. L’idée phare : l’enregistrement automatique. Au lieu qu’un administrateur configure l’outil à la main (URL, clé, secret), l’outil et la plateforme négocieraient dynamiquement un tool proxy — un gros document JSON décrivant les capacités de l’outil, les paramètres qu’il souhaite recevoir, les services qu’il expose et consomme. Le tout accompagné d’un système de capabilities et de services REST génériques.
Sur le papier, c’était élégant. En pratique, LTI 2.0 est mort-né, et son échec est un cas d’école de sur-ingénierie de standard :
- Complexité disproportionnée. Implémenter le cycle de vie du tool proxy (négociation, enregistrement, ré-enregistrement, désactivation) représentait un effort énorme pour un bénéfice marginal par rapport au copier-coller d’une clé et d’un secret. Les petits éditeurs d’outils — la majorité de l’écosystème — n’avaient ni les moyens ni l’envie.
- Sécurité inchangée. Sous le vernis REST/JSON, LTI 2.0 reposait toujours sur OAuth 1.0a et le secret symétrique. On avait complexifié l’enregistrement sans corriger le vrai problème.
- Adoption nulle. Quelques LMS (dont Moodle) ont implémenté LTI 2.0 côté plateforme, mais presque aucun outil du marché ne l’a adopté. Sans outils, pas d’intérêt pour les plateformes ; sans plateformes exigeantes, pas d’intérêt pour les outils : la boucle d’adoption ne s’est jamais amorcée.
1EdTech a fini par retirer officiellement LTI 2.0 (statut deprecated puis retrait de la spécification), et la lignée est repartie de la 1.1 pour aboutir à la 1.3. C’est pour cela que la numérotation semble revenir en arrière : 1.3 est postérieure et supérieure à 2.0. Notez que la bonne idée de LTI 2.0 — l’enregistrement automatique — n’est pas morte : elle a été réincarnée en 2020, en beaucoup plus simple, sous le nom de Dynamic Registration pour LTI 1.3 (nous la verrons au chapitre 10.3, et Moodle la supporte).
⚠️ Piège : dans le code de Moodle, vous trouverez encore des traces de LTI 2.0 (tables
lti_tool_proxies,lti_tool_settings, code de gestion des tool proxies dansmod/lti). Ne construisez rien dessus : c’est un vestige déprécié, voué à la suppression, d’un standard officiellement retiré par 1EdTech. Si vous voyez un tutoriel ou une offre d’outil mentionnant « LTI 2.0 » comme un argument, c’est un signal d’obsolescence, pas de modernité.
3.3 LTI 1.3 (2019) : la refonte sur les standards du web moderne
LTI 1.3, publié en 2019, garde le modèle conceptuel de 1.1 (launch, context, resource link, rôles) mais remplace intégralement la plomberie de sécurité par trois standards que tout développeur web moderne connaît :
-
OpenID Connect, en flux « third-party initiated login ». Le lancement devient un flux OIDC dont la particularité est d’être déclenché par un tiers (la plateforme envoie le navigateur vers l’endpoint de login initiation de l’outil, qui rebondit vers l’endpoint d’autorisation de la plateforme —
/mod/lti/auth.phpchez Moodle). Le résultat du flux est unid_token. -
JWT signés RS256. L’
id_tokenest un JSON Web Token signé avec la clé privée RSA de la plateforme (algorithme RS256). L’outil le vérifie avec la clé publique correspondante, publiée au format JWKS sur un endpoint de la plateforme (/mod/lti/certs.phpchez Moodle). Conséquences majeures : plus aucun secret symétrique partagé, rotation de clés possible sans coordination humaine (il suffit de republier le JWKS avec un nouveaukid), expiration native des jetons (claimsexpetiat), anti-rejeu parnonce, et audience explicite (aud= client_id) qui empêche de rejouer un jeton destiné à un autre outil. -
OAuth 2.0
client_credentialspour les services. Pour appeler les API (notes, roster), l’outil obtient un access token auprès de l’endpoint de token de la plateforme (/mod/lti/token.phpchez Moodle), en s’authentifiant non pas par un secret, mais par une assertion JWT signée avec la clé privée de l’outil (profil « JWT bearer client assertion », RFC 7523). Là encore : asymétrique, expirable, révocable. Les scopes OAuth délimitent finement ce que l’outil a le droit de faire (lire le roster ? écrire des notes ? créer des colonnes ?).
La symétrie est élégante : la plateforme a sa paire de clés (privée pour signer les id_tokens, publique exposée sur son JWKS) et l’outil a la sienne (privée pour signer ses client assertions et ses réponses de deep linking, publique exposée sur son JWKS — chez QuizLab, ce sera https://tool.example.com/api/lti/jwks). Chacun vérifie l’autre par sa clé publique. Aucun secret ne circule jamais.
Les données du lancement, elles, deviennent des claims JWT namespacés. Voici un id_token LTI 1.3 réaliste, tel que Moodle l’émettrait pour le lancement de notre fil rouge (payload décodé ; nous détaillerons chaque claim au chapitre 10.2) :
// Payload de l'id_token reçu par https://tool.example.com/api/lti/launch
// Émis par Moodle (https://moodle.example.com) — LtiResourceLinkRequest
{
"iss": "https://moodle.example.com",
"aud": "a1b2c3d4e5f6g7h",
"sub": "17",
"exp": 1720400600,
"iat": 1720400540,
"nonce": "9f2c4e6a8b0d1f3a5c7e",
"https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiResourceLinkRequest",
"https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
"https://purl.imsglobal.org/spec/lti/claim/deployment_id": "1",
"https://purl.imsglobal.org/spec/lti/claim/target_link_uri": "https://tool.example.com/api/lti/launch",
"https://purl.imsglobal.org/spec/lti/claim/resource_link": {
"id": "42",
"title": "Quiz chapitre 3",
"description": "Quiz noté sur les liaisons chimiques"
},
"https://purl.imsglobal.org/spec/lti/claim/context": {
"id": "101",
"label": "CHIM-L1",
"title": "Chimie L1",
"type": ["http://purl.imsglobal.org/vocab/lis/v2/course#CourseSection"]
},
"https://purl.imsglobal.org/spec/lti/claim/roles": [
"http://purl.imsglobal.org/vocab/lis/v2/membership#Learner",
"http://purl.imsglobal.org/vocab/lis/v2/system/person#User"
],
"https://purl.imsglobal.org/spec/lti/claim/tool_platform": {
"guid": "moodle.example.com",
"name": "Université Exemple",
"product_family_code": "moodle",
"version": "2026042000"
},
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation": {
"document_target": "iframe",
"return_url": "https://moodle.example.com/mod/lti/return.php?course=101&launch_container=3&instanceid=42"
},
"given_name": "Alice",
"family_name": "Martin",
"name": "Alice Martin",
"email": "alice@example.edu",
"https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": {
"scope": [
"https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
"https://purl.imsglobal.org/spec/lti-ags/scope/score",
"https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"
],
"lineitems": "https://moodle.example.com/mod/lti/services.php/101/lineitems?type_id=3",
"lineitem": "https://moodle.example.com/mod/lti/services.php/101/lineitems/12/lineitem?type_id=3"
},
"https://purl.imsglobal.org/spec/lti-nrps/claim/namesroleservice": {
"context_memberships_url": "https://moodle.example.com/mod/lti/services.php/CourseSection/101/bindings/3/memberships",
"service_versions": ["2.0"]
}
}Observez deux choses. D’abord, les claims « standards OIDC » (iss, aud, sub, exp, iat, nonce, name, email) cohabitent avec les claims LTI, dont les noms sont des URI complètes (https://purl.imsglobal.org/spec/lti/claim/...) pour éviter toute collision de namespace. Ensuite, les deux derniers claims (AGS et NRPS) sont la porte d’entrée des services : la plateforme annonce dans le jeton de lancement où se trouvent le carnet de notes et le roster de ce contexte, et quels scopes elle est prête à accorder. L’outil n’a plus à deviner ou à configurer ces URL : elles arrivent avec chaque lancement.
💡 Pour un dev React : côté QuizLab, la vérification de cet
id_tokens’écrit en une poignée de lignes avec la bibliothèquejose(celle que vous utilisez peut-être déjà avec NextAuth) :createRemoteJWKSet(new URL('https://moodle.example.com/mod/lti/certs.php'))puisjwtVerify(idToken, jwks, { issuer, audience }), plus les vérifications LTI spécifiques (noncejamais vu,statecorrespondant au cookie posé au login init,deployment_idconnu). Nous écrirons ce code pour de vrai au chapitre 10.5. Le point clé : vous n’implémentez aucune cryptographie vous-même — c’est du JWT/JWKS standard, outillé dans tous les écosystèmes.
3.4 Tableau comparatif LTI 1.1 vs LTI 1.3
| Aspect | LTI 1.1 (2011) | LTI 1.3 (2019) |
|---|---|---|
| Authentification du launch | POST de formulaire signé OAuth 1.0a (HMAC-SHA1, secret symétrique partagé) | Flux OIDC third-party initiated ; id_token JWT signé RS256 (clé asymétrique) |
| Gestion des clés | Consumer key + shared secret échangés manuellement, sans expiration ni rotation standard | Paires de clés RSA de chaque côté, publication JWKS, rotation par kid, jetons expirants (exp) |
| Risque en cas de fuite | Secret fuité = forge de lancements arbitraires (n’importe quel utilisateur, n’importe quel rôle) | Clé privée à protéger, mais jetons à durée de vie courte, audience et nonce vérifiés ; rotation rapide possible |
| Format des données | Paramètres de formulaire à plat (user_id, roles, context_id…) | Claims JWT namespacés par URI, structures JSON typées |
| Retour de notes | Basic Outcomes : XML POX, une seule note 0–1 par (user, resource link), 3 opérations | AGS : API REST/JSON, colonnes multiples (line items), scores avec statut/commentaire/horodatage, lecture des résultats |
| Liste des inscrits (roster) | Rien dans le cœur du standard (extensions propriétaires) | NRPS : service REST paginé, membres + rôles du contexte |
| Sélection de contenu | Rien dans le cœur (Content-Item Message ajouté après coup, adoption inégale) | Deep Linking 2.0, intégré à LTI Advantage, messages JWT signés dans les deux sens |
| Enregistrement de l’outil | Manuel (copier-coller clé/secret) | Manuel ou Dynamic Registration (échange automatisé de configuration) |
| Autorisation des services | Signature OAuth 1.0a du corps de chaque requête | OAuth 2.0 client_credentials + client assertion JWT, access tokens à scopes |
| Statut en 2026 | Déprécié par 1EdTech : fin de certification annoncée en 2021, retrait effectif mi-2022 ; encore présent en mode legacy dans Moodle 5.2, voué au retrait | Standard actuel recommandé, socle de LTI Advantage, certifié activement |
4. LTI Advantage : le trio de services qui change tout
Un point de terminologie d’abord, car la confusion est universelle : LTI 1.3 et LTI Advantage ne sont pas synonymes. LTI 1.3 est la spécification du cœur (le lancement sécurisé, les claims, le modèle de sécurité). LTI Advantage est un label de conformité défini par 1EdTech qui désigne l’ensemble : LTI 1.3 core + trois services complémentaires :
- AGS — Assignment and Grade Services : le carnet de notes.
- NRPS — Names and Role Provisioning Services : le roster.
- Deep Linking 2.0 : la sélection de contenu.
Un outil peut être « LTI 1.3 » sans implémenter aucun des trois services (il ne fait que du lancement). Mais dans la pratique du marché, c’est LTI Advantage qui est devenu le standard de fait exigé dans les appels d’offres. Passons chaque service en revue.
4.1 AGS — Assignment and Grade Services : le carnet de notes programmable
AGS est le successeur, méconnaissablement amélioré, de Basic Outcomes. Il repose sur trois concepts :
- Le line item : une colonne du carnet de notes, avec un intitulé, un score maximum, des dates, et un rattachement optionnel à un resource link. Là où Basic Outcomes imposait « une note par lien », AGS permet à l’outil de créer plusieurs colonnes : une pour le quiz, une pour le bonus de rapidité, une par compétence évaluée.
- Le score : la publication d’un résultat pour un utilisateur dans un line item. Un score porte bien plus qu’un nombre : un statut d’activité (
activityProgress: l’étudiant a-t-il commencé, soumis, terminé ?), un statut de notation (gradingProgress: note définitive, en attente de correction manuelle ?), un commentaire, un horodatage. - Le result : la lecture des résultats existants dans une colonne (ce que le carnet de notes contient effectivement, après d’éventuels arrondis ou surcharges par l’enseignant).
Chaque famille d’opérations a son scope OAuth 2.0 (.../scope/lineitem, .../scope/score, .../scope/result.readonly), ce qui permet à la plateforme d’accorder à un outil le droit de pousser des scores sans celui de lire les résultats des autres, par exemple.
Concrètement, quand Alice termine son quiz dans QuizLab avec 17/20, notre serveur Next.js fera (chapitre 10.4 pour le détail) :
// POST {lineitem}/scores — avec Authorization: Bearer <access_token AGS>
// Content-Type: application/vnd.ims.lis.v1.score+json
{
"userId": "17",
"scoreGiven": 17,
"scoreMaximum": 20,
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"comment": "Bonne maîtrise des liaisons covalentes ; revoir les liaisons ioniques.",
"timestamp": "2026-07-08T14:32:00+02:00"
}…et la note apparaît dans le carnet de notes Moodle du cours « Chimie L1 », dans la colonne « Quiz chapitre 3 », comme si l’enseignant l’avait saisie. Cas d’usage type : toute activité évaluée exécutée hors du LMS — quiz, devoir de programmation auto-corrigé, simulation notée, certification. C’est le service qui supprime définitivement le CSV recopié à la main.
⚠️ Piège : le claim
subdu lancement et leuserIdattendu par AGS/NRPS sont le même identifiant — l’ID LTI de l’utilisateur côté plateforme — mais votre outil doit le stocker au moment du lancement pour pouvoir pousser une note plus tard. Si QuizLab n’enregistre que « l’utilisateur QuizLab n° 508 » sans conserver lesubLTI (et l’URL du line item, et le triplet du tenant), il sera incapable de renvoyer la note. Pensez votre modèle de données AGS dès le premier lancement, pas au moment de pousser la première note.
4.2 NRPS — Names and Role Provisioning Services : le roster
NRPS répond à une question que le lancement seul ne peut pas résoudre : « qui est inscrit dans ce cours ? ». Le lancement ne vous révèle les utilisateurs qu’un par un, au moment où ils cliquent. Or beaucoup de scénarios exigent de connaître la liste complète en amont : pré-créer une copie d’examen pour chaque inscrit, constituer des groupes, afficher à l’enseignant un tableau de bord « 12 étudiants sur 34 ont commencé le quiz » — y compris les 22 qui n’ont jamais lancé l’outil.
NRPS est un service REST simple : l’URL du roster du contexte arrive dans le claim namesroleservice du lancement (voir notre id_token plus haut), l’outil obtient un access token avec le scope https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly, et récupère une liste paginée :
// GET {context_memberships_url} — extrait de réponse NRPS 2.0
// Accept: application/vnd.ims.lti-nrps.v2.membershipcontainer+json
{
"id": "https://moodle.example.com/mod/lti/services.php/CourseSection/101/bindings/3/memberships",
"context": { "id": "101", "label": "CHIM-L1", "title": "Chimie L1" },
"members": [
{
"status": "Active",
"user_id": "17",
"name": "Alice Martin",
"given_name": "Alice",
"family_name": "Martin",
"email": "alice@example.edu",
"roles": ["http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"]
},
{
"status": "Active",
"user_id": "3",
"name": "Pr. Bernard Curie",
"given_name": "Bernard",
"family_name": "Curie",
"email": "b.curie@example.edu",
"roles": ["http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"]
}
]
}Cas d’usage type : tableaux de bord enseignant, provisioning anticipé, constitution de groupes, relances (« ces 5 étudiants n’ont pas commencé »). Notez le versant vie privée : NRPS expose noms et emails de tous les inscrits, c’est pourquoi Moodle le soumet à une activation explicite par l’administrateur lors de l’enregistrement de l’outil, et pourquoi vos conditions de traitement de données (RGPD) doivent le couvrir.
4.3 Deep Linking 2.0 : l’enseignant choisit le contenu depuis l’outil
Sans deep linking, la création d’une activité LTI est frustrante pour l’enseignant : il crée un lien « QuizLab » dans son cours… mais comment dire quel quiz ce lien doit ouvrir ? Historiquement, on collait un identifiant dans un « paramètre personnalisé » — c’est-à-dire qu’on demandait à un enseignant de chimie de copier quiz_id=8742 dans un champ de formulaire technique. Expérience utilisateur désastreuse.
Deep Linking 2.0 renverse le flux. Quand l’enseignant ajoute l’activité, la plateforme lance l’outil avec un message spécial, LtiDeepLinkingRequest (au lieu du LtiResourceLinkRequest habituel). L’outil affiche alors sa propre interface de sélection — chez QuizLab : la bibliothèque de quiz de l’enseignant, avec recherche et prévisualisation. L’enseignant choisit « Quiz chapitre 3 », et l’outil renvoie à la plateforme un LtiDeepLinkingResponse : un JWT signé par la clé privée de l’outil cette fois, contenant les content items sélectionnés :
// Extrait du payload du JWT LtiDeepLinkingResponse signé par QuizLab
// et auto-posté vers le deep_link_return_url de Moodle
{
"iss": "a1b2c3d4e5f6g7h",
"aud": "https://moodle.example.com",
"https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiDeepLinkingResponse",
"https://purl.imsglobal.org/spec/lti/claim/deployment_id": "1",
"https://purl.imsglobal.org/spec/lti-dl/claim/content_items": [
{
"type": "ltiResourceLink",
"title": "Quiz chapitre 3 — Les liaisons chimiques",
"url": "https://tool.example.com/api/lti/launch",
"custom": { "quiz_id": "8742" },
"lineItem": { "scoreMaximum": 20, "label": "Quiz chapitre 3" }
}
]
}Moodle crée alors l’activité automatiquement, avec le bon titre, les bons paramètres personnalisés, et même la colonne de notes pré-dimensionnée (lineItem). Aux lancements suivants, les étudiants qui cliquent sur cette activité arrivent directement sur le quiz 8742. Cas d’usage type : toute bibliothèque de contenus — banques d’exercices, vidéos, chapitres de manuels numériques, contenus H5P. Remarquez au passage l’inversion des rôles JWT : dans la réponse de deep linking, c’est l’outil qui signe (iss = client_id de l’outil) et la plateforme qui vérifie via le JWKS de l’outil (https://tool.example.com/api/lti/jwks). La symétrie cryptographique de LTI 1.3 prend ici tout son sens.
💡 Pour un dev React : le deep linking est l’endroit où votre savoir-faire front brille le plus. L’interface de sélection est une page React ordinaire (rendue dans une iframe ou une popup depuis Moodle), avec votre design system, votre recherche instantanée, vos prévisualisations. La seule contrainte LTI est la sortie : à la validation, votre serveur signe le
LtiDeepLinkingResponseet le navigateur l’auto-poste vers ledeep_link_return_urlfourni dans la requête — un simple<form method="POST">avec un champ cachéJWT, soumis enuseEffectou côté serveur. Tout le reste est du produit, pas du protocole.
4.4 La certification 1EdTech : pourquoi elle compte commercialement
1EdTech ne se contente pas de publier des spécifications : il opère un programme de certification. Un éditeur soumet son outil (ou sa plateforme) à une suite de tests officielle qui vérifie la conformité — validation correcte des signatures et des nonces, rejet des jetons expirés, respect des formats AGS/NRPS/Deep Linking, gestion des cas d’erreur. Les produits qui passent obtiennent le label, dont le niveau le plus complet est LTI Advantage Complete (core + les trois services), et figurent dans le catalogue public des produits certifiés de 1EdTech.
Pourquoi est-ce important si vous développez un outil ?
- C’est un critère d’achat. Les universités et les districts scolaires exigent de plus en plus la certification LTI Advantage dans leurs appels d’offres, précisément parce qu’elle leur garantit que l’intégration marchera avec leur LMS sans projet d’intégration sur mesure. « Certifié LTI Advantage » est à l’EdTech ce que « SOC 2 » est au SaaS B2B : un ticket d’entrée.
- C’est un garde-fou de sécurité. La suite de certification teste les cas que les implémentations naïves ratent : jeton rejoué,
statemanquant, signature d’une mauvaise clé,deployment_idinconnu. Passer la certification, c’est avoir prouvé qu’on ne fait pas partie des outils forgeables. - C’est un signal d’interopérabilité réelle. Les specs laissent des marges d’interprétation ; la certification aligne tout le monde sur la même lecture. Un outil certifié et une plateforme certifiée qui ne fonctionnent pas ensemble, c’est rarissime.
Moodle est certifié LTI Advantage — et il l’est dans les deux rôles : comme platform (le cas usuel : Moodle lance des outils externes) et comme tool (Moodle publié comme outil dans un autre LMS, via enrol_lti — nous y venons). C’est un point fort de Moodle dans le paysage : peu de LMS tiennent les deux rôles de façon certifiée.
📚 Aller plus loin : le catalogue des produits certifiés est public sur
https://site.imsglobal.org/certifications(recherchez « Moodle » pour voir les certifications platform et tool). Les spécifications elles-mêmes sont surhttps://www.imsglobal.org/activity/learning-tools-interoperability: LTI 1.3 core (/spec/lti/v1p3), AGS (/spec/lti-ags/v2p0), NRPS (/spec/lti-nrps/v2p0), Deep Linking (/spec/lti-dl/v2p0), Dynamic Registration (/spec/lti-dr/v1p0). Lisez-les au moins en diagonale : elles sont bien rédigées, et ce cours y fera référence en permanence.
5. L’état des lieux dans Moodle 5.2
Venons-en à notre LMS. Moodle 5.2 (sorti le 20 avril 2026, PHP 8.3 minimum) occupe une position singulière : il implémente LTI dans les deux directions, via deux composants distincts qu’il ne faut surtout pas confondre.
5.1 mod_lti : Moodle comme plateforme
mod_lti (le plugin d’activité « Outil externe », répertoire mod/lti/ du code source) fait de Moodle une platform LTI. C’est le composant que vous utiliserez dans 95 % des cas : l’administrateur enregistre un outil externe (QuizLab), l’enseignant ajoute une activité « Outil externe » dans son cours, les étudiants la lancent. C’est mod_lti qui expose les trois endpoints de plateforme de notre convention :
| Endpoint | Rôle |
|---|---|
https://moodle.example.com/mod/lti/auth.php | Endpoint d’autorisation OIDC : reçoit la requête d’authentification de l’outil, vérifie la session Moodle de l’utilisateur, construit et signe l’id_token, puis l’auto-poste vers l’outil. |
https://moodle.example.com/mod/lti/token.php | Endpoint de token OAuth 2.0 : délivre les access tokens client_credentials (avec client assertion JWT) pour les appels de services AGS/NRPS. |
https://moodle.example.com/mod/lti/certs.php | JWKS de la plateforme : les clés publiques RSA avec lesquelles vérifier les id_token signés par Moodle. |
Les services eux-mêmes (AGS, NRPS) sont implémentés comme des sous-plugins de type ltiservice (mod/lti/service/gradebookservices, mod/lti/service/memberships…) et servis via mod/lti/services.php. Moodle plateforme supporte l’intégralité de LTI Advantage, plus la Dynamic Registration : au lieu de saisir à la main les URL, scopes et clés de l’outil, l’administrateur colle une unique « URL d’enregistrement » fournie par l’outil, et l’échange de configuration se fait automatiquement (protocole que nous détaillerons au chapitre 10.3, et que nous implémenterons côté QuizLab au chapitre 10.5).
5.2 enrol_lti : Moodle comme outil (« Publish as LTI tool »)
enrol_lti (plugin d’inscription « Publier en tant qu’outil LTI », répertoire enrol/lti/) joue le rôle inverse : il fait de Moodle un tool LTI. Un cours ou une activité de votre Moodle peut être publié et lancé depuis un autre LMS — un Canvas, un Blackboard, ou un autre Moodle. Les utilisateurs de la plateforme distante sont automatiquement provisionnés et inscrits dans le cours Moodle publié, et leurs notes peuvent remonter vers la plateforme d’origine via AGS.
Le cas d’usage typique : une université dont le LMS officiel est Canvas, mais dont le département de langues possède des contenus riches dans un Moodle historique ; ou un organisme de formation qui vend l’accès à ses cours Moodle à des entreprises clientes utilisant chacune leur propre LMS. C’est cette double implémentation, platform et tool, qui vaut à Moodle sa double certification LTI Advantage.
5.3 Le legacy 1.1/2.0 : encore là, mais en sursis
Dans Moodle 5.2, LTI 1.1 et LTI 2.0 fonctionnent encore en mode « legacy », à la fois dans mod_lti et dans enrol_lti : vous pouvez toujours configurer un outil avec une consumer key et un shared secret, et les vieux outils 1.1 du marché continuent de se lancer. Mais ces versions sont officiellement dépréciées — par 1EdTech au niveau du standard (fin de certification annoncée en 2021, retrait effectif mi-2022), et par Moodle au niveau du produit, où elles sont vouées à être retirées dans une version future. LTI Advantage est la voie recommandée pour toute nouvelle intégration, sans exception.
Concrètement, pour vous :
- Nouvel outil à intégrer ou à développer → LTI 1.3 / LTI Advantage, point final. N’apprenez la mécanique 1.1 que pour la culture et le débogage d’existant.
- Installation existante avec des outils 1.1 → planifiez la migration. La plupart des grands éditeurs (Turnitin, H5P.com…) proposent depuis longtemps leurs enregistrements 1.3 ; le guide de migration 1EdTech et le claim de compatibilité
lti1p1permettent de faire le pont entre les identifiants utilisateurs des deux générations.
⚠️ Piège : dans l’interface d’administration de Moodle (
Administration du site > Plugins > Modules d'activité > Outil externe > Gérer les outils), le formulaire de configuration manuelle propose toujours le choix « LTI 1.0/1.1 » dans le sélecteur de version, avec les champs clé/secret. Il est donc parfaitement possible, en 2026, de créer par inadvertance une intégration sur un protocole déprécié — le formulaire ne vous en empêchera pas. Vérifiez systématiquement que « LTI 1.3 » est sélectionné, et préférez la Dynamic Registration quand l’outil la propose : elle configure d’office du 1.3, sans erreur de saisie possible.
5.4 Vue d’ensemble
Moodle 5.2 et LTI — vue d'ensemble
Moodle = PLATFORM (mod_lti) Moodle = TOOL (enrol_lti)
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ https://moodle.example.com │ │ Un autre LMS (Canvas, │
│ │ │ Blackboard, autre Moodle…) │
│ /mod/lti/auth.php (OIDC) │ │ │ lance │
│ /mod/lti/token.php (OAuth2) │ │ ▼ │
│ /mod/lti/certs.php (JWKS) │ │ Cours/activité Moodle publiés │
│ /mod/lti/services.php (AGS, │ │ via « Publish as LTI tool » │
│ NRPS) │ │ (auto-provisioning + notes │
│ │ lance │ │ remontées via AGS) │
│ ▼ │ └────────────────────────────────┘
│ Outils externes : │
│ QuizLab, H5P.com, Turnitin… │ LTI 1.1 / 2.0 : mode legacy,
└────────────────────────────────┘ déprécié, retrait programmé.
LTI Advantage : recommandé,
Dynamic Registration : supportée certifié (platform + tool).📚 Aller plus loin : la documentation développeur de Moodle sur LTI vit sur
https://moodledev.io(rubriques sur mod_lti, les sous-pluginsltiserviceetltisource, et l’API LTI) ; la documentation d’administration surhttps://docs.moodle.org(pages « External tool » et « Publish as LTI tool »). Gardez aussi sous la main la bibliothèque de référence PHP de 1EdTech (celtic-project/LTI-PHP) : ce n’est pas celle que nous utiliserons pour QuizLab (nous serons en TypeScript), mais sa lecture éclaire beaucoup de choix d’implémentation de Moodle.
6. Cas d’usage réels : qui utilise LTI, et comment
Pour ancrer tout cela dans le concret, passons en revue quatre familles d’outils LTI que vous rencontrerez dans la vraie vie, en identifiant à chaque fois quels services LTI Advantage ils mobilisent et pourquoi. Puis nous présenterons officiellement notre fil rouge.
6.1 H5P.com : la bibliothèque de contenus interactifs
H5P est le framework open source de contenus interactifs (vidéos interactives, quiz, présentations, jeux de glisser-déposer) que vous avez déjà croisé dans ce cours : Moodle intègre nativement un moteur H5P. Mais l’offre SaaS H5P.com va plus loin (types de contenus premium, analytics, gestion centralisée) et s’intègre aux LMS via LTI :
- Deep Linking 2.0 est le cœur de l’expérience : l’enseignant clique « ajouter une activité H5P », l’iframe H5P.com s’ouvre sur sa bibliothèque de contenus, il choisit (ou crée sur place) une vidéo interactive, valide — et l’activité est créée dans Moodle avec le bon contenu lié. C’est l’exemple canonique de l’outil-bibliothèque.
- AGS remonte les scores : chaque interaction notée (un quiz intégré dans la vidéo) pousse son résultat dans le carnet de notes du cours.
- NRPS sert aux tableaux de bord : montrer à l’enseignant qui a regardé la vidéo et jusqu’où, y compris les étudiants qui ne l’ont pas ouverte.
6.2 Turnitin : la détection de similarités
Turnitin, le service de détection de plagiat et d’aide à l’évaluation, est l’un des plus anciens et des plus gros utilisateurs de LTI (des dizaines de milliers d’établissements). Son intégration LTI 1.3 illustre le pattern « outil de workflow enseignant » :
- Le launch différencié par rôle est essentiel : l’étudiant qui lance l’activité voit l’interface de dépôt de devoir ; l’enseignant qui lance la même activité voit la liste des soumissions avec les rapports de similarité et l’interface d’annotation. Un seul resource link, deux expériences, zéro configuration — c’est le claim
rolesqui fait tout. - AGS renvoie la note attribuée par l’enseignant dans Turnitin vers le carnet de notes Moodle, avec
gradingProgresspassant dePending(copie soumise, non corrigée) àFullyGraded. - NRPS permet d’afficher les non-rendus : la liste des inscrits moins la liste des soumissions.
6.3 Labs virtuels et auto-correction de code : le pattern Labster/CodeGrade
Troisième famille : les environnements d’exercice lourds, impossibles à héberger dans le LMS. Les simulations scientifiques 3D type Labster (laboratoires virtuels de biologie, chimie, physique) et les plateformes d’évaluation de code type CodeGrade (l’étudiant soumet du code, des tests automatiques s’exécutent dans des conteneurs) sont des applications à part entière, avec leurs GPU, leurs sandboxes d’exécution, leur infrastructure propre. LTI est exactement le joint qu’il leur faut :
- Le launch fournit le SSO et le contexte : l’étudiant arrive dans la simulation ou l’IDE en ligne déjà identifié, sur l’exercice correspondant à l’activité cliquée (paramètre custom posé par deep linking).
- Deep Linking permet à l’enseignant de choisir quelle simulation ou quel devoir de code rattacher à chaque activité, depuis le catalogue de l’outil.
- AGS est utilisé de façon asynchrone, et c’est le point architecturalement intéressant : la correction d’une soumission de code peut prendre plusieurs minutes (compilation, suites de tests) et se termine après que l’étudiant a fermé son navigateur. Le score est poussé par un worker serveur, via un access token
client_credentialsobtenu au moment du besoin — la preuve par l’exemple que les services ne dépendent pas de la session de lancement. - NRPS sert au pré-provisioning : créer les dépôts ou environnements de chaque étudiant avant le premier lancement, pour que le TP démarre sans latence.
6.4 Éditeurs de contenus et manuels numériques
Quatrième famille : les éditeurs (au sens maisons d’édition) de manuels et de banques d’exercices — pensez aux plateformes des grands éditeurs universitaires. Leur problème : vendre l’accès à des contenus premium à travers le LMS de chaque client, sans y copier les contenus. Leur usage de LTI :
- Deep Linking pour insérer un chapitre, une section ou un exercice précis du manuel comme item de cours.
- Le launch pour contrôler la licence : le triplet
(iss, client_id, deployment_id)identifie l’établissement client et déclenche le contrôle d’accès commercial (cet établissement a-t-il souscrit à ce manuel ?). C’est le deployment_id dans son rôle de pivot de facturation, exactement comme décrit en section 2.3. - AGS pour les exercices auto-corrigés du manuel.
6.5 Le fil rouge du cours : QuizLab, votre outil Next.js
Et enfin, le cas d’usage qui nous occupera jusqu’à la fin de cette partie : QuizLab, l’application de quiz que vous allez construire. Cadrons-la dès maintenant :
- Stack : Next.js (App Router) + TypeScript, hébergée sur
https://tool.example.com. - Routes LTI :
/api/lti/login(login initiation OIDC),/api/lti/launch(réception et vérification de l’id_token, création de session),/api/lti/jwks(publication de la clé publique de l’outil). - Plateforme : notre Moodle 5.2 sur
https://moodle.example.com(issuer), qui nous attribuera le client_ida1b2c3d4e5f6g7het le deployment_id1. - Fonctionnel : l’enseignant choisit un quiz de sa bibliothèque via Deep Linking ; l’étudiant le passe après un launch SSO ; la note remonte via AGS (avec un score partiel
activityProgress: "InProgress"pendant le quiz, puisCompleted/FullyGradedà la soumission) ; le tableau de bord enseignant croise NRPS et les données locales pour afficher qui a fait quoi. Autrement dit : LTI Advantage complet, les quatre briques, sur un cas réaliste.
La construction complète (chapitre 10.5) vous fera toucher chaque pièce du protocole depuis le côté outil — la meilleure position pour comprendre ce que Moodle fait de l’autre côté.
💡 Pour un dev React : une décision d’architecture à anticiper dès maintenant pour QuizLab — la session. Un outil LTI vit très souvent dans une iframe du LMS, et les cookies posés dans une iframe sont des cookies tiers : ils exigent
SameSite=None; Secure, et certains navigateurs (Safari et son ITP, navigateurs avec protection renforcée) les bloquent purement et simplement. Les stratégies de contournement (ouverture en nouvel onglet, vérification de cookie au launch, ou session portée par un jeton propre plutôt que par cookie) seront traitées au chapitre 10.5 — mais si vous commencez à maquetter QuizLab avant, ne partez pas du postulat « NextAuth avec cookie par défaut et tout ira bien » : dans une iframe Moodle, non.
7. Panorama de la Partie 10
Vous avez maintenant la carte complète du territoire. Voici comment les chapitres suivants vont l’explorer, chacun s’appuyant sur les concepts posés ici :
Chapitre 10.2 — LTI 1.3 sous le capot. La plongée technique dans le protocole : le flux OIDC third-party initiated étape par étape (login initiation, requête d’authentification, state et nonce, auto-POST de l’id_token), l’anatomie complète de chaque claim du JWT de lancement, la vérification de signature via JWKS, le flux client_credentials avec client assertion pour les services, et les attaques que chaque mécanisme neutralise (rejeu, CSRF de login, confusion d’audience, forge de rôle). C’est le chapitre de référence protocolaire de la partie.
Chapitre 10.3 — Configurer LTI côté Moodle. Le versant administration et exploitation : enregistrer un outil dans Moodle 5.2 (manuellement et via Dynamic Registration), comprendre chaque option du formulaire (visibilité, confidentialité des données, services activés, placements), configurer les outils au niveau du site vs au niveau du cours, publier un cours Moodle avec enrol_lti, et déboguer les lancements qui échouent.
Chapitre 10.4 — Les services LTI Advantage en pratique. AGS et NRPS en profondeur : le cycle de vie des line items, la sémantique fine de activityProgress/gradingProgress, la pagination NRPS, les scopes et la négociation des tokens, et la manière dont Moodle mappe tout cela sur son carnet de notes et ses inscriptions — avec les requêtes HTTP complètes.
Chapitre 10.5 — Construire QuizLab, votre outil LTI en Next.js. Le chapitre atelier : implémentation complète des routes /api/lti/login, /api/lti/launch et /api/lti/jwks en TypeScript, gestion des clés, modèle de données multi-tenant fondé sur le triplet, deep linking, poussée de notes AGS, session en iframe, et Dynamic Registration pour une installation en un clic.
Chapitre 10.6 — Sujets avancés et production. Sécurité durcie (rotation de clés, monitoring des lancements, rate limiting du JWKS), conformité RGPD des données LTI, multi-tenancy à l’échelle, tests automatisés d’une intégration LTI, passage de la certification 1EdTech, et stratégies de migration 1.1 → 1.3 pour l’existant.
Ce qu’il faut retenir
-
LTI résout trois problèmes qui rendaient les intégrations pré-2010 intenables : l’identité dupliquée (résolue par le SSO fédéré), l’absence de contexte (résolue par les claims normalisés : utilisateur, cours, resource link, rôles), et le retour manuel des notes (résolu par les services de carnet de notes). Son bénéfice stratégique : une seule intégration pour tous les LMS certifiés — Moodle, Canvas, Blackboard, Brightspace, Sakai.
-
Le vocabulaire est structurant : la platform (le LMS, ex-« consumer ») lance le tool (l’app externe, ex-« provider ») ; le context est le cours, le resource link est le lien précis cliqué dans ce cours ; les messages passent par le navigateur, les services sont des appels serveur-à-serveur. Et surtout : un tenant est identifié par le triplet
(issuer, client_id, deployment_id)— jamais par le seulsub. -
L’histoire éclaire l’architecture : LTI 1.0/1.1 (2010-2011) reposait sur OAuth 1.0a et un secret symétrique partagé — simple mais fragile (pas d’expiration, spoofing intégral en cas de fuite) — avec un retour de note rudimentaire en XML POX (Basic Outcomes). LTI 2.0 (2014) est mort de sa complexité (tool proxy, adoption nulle) et a été retiré. LTI 1.3 (2019) refonde la sécurité sur OIDC third-party initiated + JWT RS256 + OAuth 2.0 client_credentials, avec des clés asymétriques publiées en JWKS des deux côtés. LTI 1.1 est officiellement déprécié par 1EdTech (fin de certification 2021, retrait mi-2022). 1EdTech est le nouveau nom d’IMS Global depuis mai 2022.
-
LTI Advantage = LTI 1.3 core + AGS + NRPS + Deep Linking 2.0 : le carnet de notes programmable (colonnes multiples, scores riches avec statuts), le roster du cours (membres et rôles, y compris ceux qui n’ont jamais lancé l’outil), et la sélection de contenu par l’enseignant depuis l’interface de l’outil. La certification 1EdTech (LTI Advantage Complete) est devenue le ticket d’entrée commercial pour vendre un outil aux institutions.
-
Dans Moodle 5.2 :
mod_ltifait de Moodle une plateforme LTI complète (endpoints/mod/lti/auth.php,/mod/lti/token.php,/mod/lti/certs.php),enrol_lti(« Publish as LTI tool ») le fait fonctionner comme outil dans d’autres LMS, et Moodle est certifié LTI Advantage dans les deux rôles. LTI 1.1/2.0 subsistent en mode legacy mais sont dépréciés et voués au retrait ; la Dynamic Registration est supportée et recommandée pour enregistrer les outils modernes. -
Pour vous, dev web : LTI 1.3 est un OIDC spécialisé éducation. Vos réflexes JWT/JWKS/OAuth s’appliquent directement ; les spécificités à apprendre sont le flux de login initié par la plateforme, les claims métier, la discipline multi-tenant du triplet, et les contraintes de session en iframe.
Au prochain chapitre, nous ouvrons le capot : 10.2 — LTI 1.3 sous le capot suit à la trace chaque redirection, chaque paramètre et chaque claim d’un lancement réel entre https://moodle.example.com et QuizLab, et démonte les mécanismes de sécurité (state, nonce, signatures, audiences) un par un — avec, pour chacun, l’attaque précise qu’il empêche. Munissez-vous d’un décodeur de JWT : on va lire des jetons.