Chapitre 9.4 — Le flux de lancement
⏱️ TL;DR — Le lancement LTI 1.3 se fait en quatre temps : (1) l’élève clique, la plateforme envoie une amorce de login OIDC à ton outil ; (2) ton outil redirige le navigateur vers l’endpoint d’autorisation de la plateforme avec une requête d’auth (portant
stateetnonce) ; (3) la plateforme POST leid_token(JWT signé) à tonredirect_uri; (4) ton outil valide le jeton (signature via JWKS,iss,aud,exp,nonce,state) puis rend le contenu. Maîtriser ces quatre temps, avec les contrôles de sécurité à chaque étape, c’est savoir implémenter LTI 1.3.
🎯 Objectifs
- Dérouler le flux de lancement 1.3 étape par étape.
- Savoir quel endpoint intervient à chaque temps et quels paramètres circulent.
- Placer les contrôles de sécurité (
state,nonce, signature,aud,exp) au bon moment. - Diagnostiquer un lancement qui échoue en identifiant l’étape fautive.
Vue d’ensemble : quatre temps
Détaillons chaque temps, avec ce qui circule et ce qu’on vérifie.
Temps 1 — L’amorce de login OIDC
L’élève clique sur l’activité dans le LMS. La plateforme n’envoie pas encore le lancement : elle envoie d’abord une amorce de login (OIDC third-party initiated login) vers l’endpoint de login de ton outil (une URL que tu as enregistrée). Paramètres typiques :
POST /lti/login HTTP/1.1
Host: studio.formacampus.fr
Content-Type: application/x-www-form-urlencoded
iss=https://moodle.ecole.fr
&login_hint=u-2342
&target_link_uri=https://studio.formacampus.fr/lti/launch
<i_message_hint=res-4815
&client_id=formacampus-studio-client-id
<i_deployment_id=dep-1iss: quelle plateforme t’appelle. C’est la clé pour retrouver ta configuration de cette plateforme (endpoints,jwks_uri,client_id).login_hint,lti_message_hint: des opaques que tu devras renvoyer tels quels à l’étape suivante (la plateforme s’en sert pour retrouver le contexte).target_link_uri: l’URL de lancement finale visée.client_id,lti_deployment_id: présents pour lever l’ambiguïté si tu sers plusieurs configurations.
💡 Réflexe — À réception de l’amorce, génère et mémorise un
state(anti-CSRF) et unnonce(anti-rejeu) que tu inclueras dans la requête d’auth. Stocke-les côté session/serveur pour les revérifier au temps 4. C’est le moment où tu poses tes garde-fous.
Temps 2 — La requête d’autorisation
Ton outil répond à l’amorce en redirigeant le navigateur vers l’endpoint d’autorisation OIDC de la plateforme (une URL que tu as, elle aussi, enregistrée), avec une requête d’auth :
GET /mod/lti/auth.php?
scope=openid
&response_type=id_token
&response_mode=form_post
&prompt=none
&client_id=formacampus-studio-client-id
&redirect_uri=https://studio.formacampus.fr/lti/launch
&login_hint=u-2342
<i_message_hint=res-4815
&state=st-9f2a... (ton state, memorise)
&nonce=nc-71b3... (ton nonce, memorise)Points importants :
response_type=id_tokenetresponse_mode=form_post: tu demandes que la plateforme te renvoie unid_tokenpar POST de formulaire.prompt=none: l’utilisateur est déjà authentifié dans le LMS ; on ne veut aucune interaction supplémentaire.redirect_uri: doit être une URL pré-enregistrée chez la plateforme (sinon elle refuse). C’est là que le jeton sera POSTé.login_hint/lti_message_hint: renvoyés tels quels (reçus au temps 1).state/nonce: ceux que tu viens de générer et de mémoriser.
⚠️ Piège — Un
redirect_urinon enregistré (ou qui diffère d’un caractère,httpvshttps, slash final) fait échouer le lancement côté plateforme, souvent avec un message obscur. Laredirect_uride la requête doit correspondre exactement à celle enregistrée. C’est l’une des erreurs de configuration les plus fréquentes.
Temps 3 — La plateforme renvoie le id_token
La plateforme vérifie la requête (client connu, redirect_uri enregistrée, utilisateur authentifié) puis POST un formulaire vers ta redirect_uri, contenant le id_token et le state que tu avais envoyé :
POST /lti/launch HTTP/1.1
Host: studio.formacampus.fr
Content-Type: application/x-www-form-urlencoded
id_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVC... (le JWT signe)
&state=st-9f2a...Le id_token est le JWT vu au chapitre 9.3 : header (alg, kid), payload (les claims LTI + iss, aud, exp, nonce), signature. Tu ne peux pas encore lui faire confiance : il faut le valider.
Temps 4 — Validation, puis rendu
C’est l’étape critique. Avant d’afficher quoi que ce soit, ton outil enchaîne les contrôles :
// Pseudo-code de validation d'un lancement LTI 1.3
function handleLaunch(req, session) {
const { id_token, state } = req.body
// 1. Anti-CSRF : le state doit correspondre a celui memorise au temps 2
if (state !== session.state) throw new Error("state invalide")
// 2. Decoder le header pour lire le kid, puis recuperer la cle publique
const { kid } = decodeHeader(id_token)
const publicKey = fetchFromJwks(platform.jwksUri, kid) // avec cache
// 3. Verifier la SIGNATURE avec la cle publique de la plateforme
const claims = verifyJwtSignature(id_token, publicKey) // RS256
// 4. Verifier les claims de securite
assert(claims.iss === platform.issuer) // bonne plateforme
assert(claims.aud === tool.clientId) // c'est bien pour moi
assert(claims.exp > now()) // non expire
assert(claims.nonce === session.nonce) // anti-rejeu
// 5. SEULEMENT MAINTENANT : lire le contexte et rendre
const roles = claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
renderTool({ userId: claims.sub, roles })
}L’ordre compte : d’abord state, ensuite la signature (via JWKS), puis iss/aud/exp/nonce, et seulement après on lit les claims métier (sub, roles, contexte). Sauter un contrôle, c’est ouvrir une faille.
⚠️ Piège — Afficher l’outil avant d’avoir tout validé (« je regarde vite si le lancement marche, je sécuriserai après »). En LTI, la validation EST le lancement : un rendu avant validation, c’est un outil qui fait confiance à un jeton potentiellement forgé. On ne rend rien tant que les cinq contrôles ne sont pas passés.
🔌 Côté intégration — Quand un lancement échoue chez un client, ce diagramme est ton outil de diagnostic : demande-toi à quel temps ça casse. Amorce non reçue → endpoint de login mal enregistré (temps 1). Redirection refusée →
redirect_urinon enregistrée (temps 2). Pas deid_token→client_id/config plateforme erronés (temps 3).id_tokenreçu mais rejeté → signature (mauvaisjwks_uri/kid),aud(mauvaisclient_id),exp(horloges désynchronisées) ounonce(temps 4). Nommer l’étape, c’est déjà à moitié résoudre.
🧭 Sur FormaCampus — Premier lancement réel du studio depuis le Moodle d’une école : l’
id_tokenarrive mais est rejeté surexp. Diagnostic : l’horloge du serveur du studio retardait de plusieurs minutes, rendant le jeton « déjà expiré ». Correction : synchroniser l’heure (NTP) et tolérer un léger décalage (clock skew). Le lancement passe. Le flux à quatre temps a permis d’isoler l’étape fautive en minutes.
📚 La spec — Ce flux est celui de LTI 1.3 Core (1EdTech), qui réutilise le profil OpenID Connect implicit/form_post : third-party initiated login,
response_mode=form_post,id_tokensigné. Les paramètres exacts (login_hint,lti_message_hint,prompt=none…) et l’ordre des contrôles sont normés — respecte-les à la lettre, la sécurité en dépend. Détails sur1edtech.org.
✏️ Exercices
Exercice 1 — Remets dans l’ordre. Voici quatre événements en désordre : (A) la plateforme POST le id_token à ta redirect_uri ; (B) tu vérifies la signature via le JWKS ; (C) la plateforme envoie l’amorce de login à ton endpoint ; (D) tu rediriges vers l’endpoint d’auth avec state et nonce. Donne l’ordre correct et nomme le temps de chacun.
✅ Solution
Ordre : C → D → A → B. (C) = temps 1 (amorce de login OIDC). (D) = temps 2 (requête d’autorisation, avec state/nonce). (A) = temps 3 (la plateforme renvoie le id_token par form POST). (B) = temps 4 (validation : la signature n’est qu’une partie — il faut aussi state, iss, aud, exp, nonce avant de rendre).
Exercice 2 — Diagnostique. Un client rapporte : « Je clique sur l’activité, je suis redirigé, puis j’atterris sur une page d’erreur de mon outil qui dit nonce invalide. » À quel temps est le problème, et quelles causes plausibles ?
✅ Solution
Le problème est au temps 4 (validation) : le id_token est bien arrivé, mais le nonce reçu ne correspond pas à celui mémorisé au temps 2. Causes plausibles : le nonce n’a pas été stocké/retrouvé correctement (session perdue entre temps 2 et 4, cookies bloqués en iframe SameSite, plusieurs instances serveur sans session partagée), ou un nonce réutilisé (rejeu). Piste fréquente en LTI : les cookies tiers/iframe — l’outil s’affiche souvent dans une iframe du LMS, ce qui casse le stockage de session si les cookies SameSite ne sont pas configurés pour ce contexte.
🧠 Quiz de révision
1. Quels sont les quatre temps du lancement LTI 1.3 ?
(1) Amorce de login OIDC (plateforme → endpoint login de l’outil). (2) Requête d’auth (outil redirige le navigateur vers l’endpoint d’auth de la plateforme, avec state/nonce). (3) La plateforme POST le id_token à la redirect_uri. (4) L’outil valide puis rend.
2. À quoi servent state et nonce ?
state et nonce ?state = anti-CSRF (corréler la réponse à ta requête). nonce = anti-rejeu (empêcher qu’un id_token capté soit réutilisé). Tu les génères au temps 1/2 et les revérifies au temps 4.
3. Pourquoi un lancement échoue-t-il souvent sur la redirect_uri ?
redirect_uri ?Parce qu’elle doit correspondre exactement à une URL pré-enregistrée chez la plateforme. Une différence de http/https, de slash final ou de casse suffit à faire refuser la requête d’auth (temps 2).
4. Dans quel ordre valider le id_token ?
id_token ?state → signature (via JWKS, clé du bon kid) → iss → aud (= mon client_id) → exp → nonce. Puis seulement lire sub, roles, contexte et rendre. On ne rend rien avant.
5. Pourquoi les cookies/iframe posent-ils souvent problème en LTI ?
L’outil s’affiche fréquemment dans une iframe du LMS : les cookies de session peuvent être bloqués (politiques SameSite/tiers), ce qui fait perdre le state/nonce mémorisés entre les temps 2 et 4 et casse la validation. Il faut configurer les cookies pour ce contexte (ou un stockage adapté).
Chapitre suivant : Enregistrement & claims — comment plateforme et outil se connaissent, et comment lire le contenu d’un lancement.