Skip to Content

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 state et nonce) ; (3) la plateforme POST le id_token (JWT signé) à ton redirect_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 &lti_message_hint=res-4815 &client_id=formacampus-studio-client-id &lti_deployment_id=dep-1
  • iss : 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 un nonce (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 &lti_message_hint=res-4815 &state=st-9f2a... (ton state, memorise) &nonce=nc-71b3... (ton nonce, memorise)

Points importants :

  • response_type=id_token et response_mode=form_post : tu demandes que la plateforme te renvoie un id_token par 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_uri non enregistré (ou qui diffère d’un caractère, http vs https, slash final) fait échouer le lancement côté plateforme, souvent avec un message obscur. La redirect_uri de 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_uri non enregistrée (temps 2). Pas de id_tokenclient_id/config plateforme erronés (temps 3). id_token reçu mais rejeté → signature (mauvais jwks_uri/kid), aud (mauvais client_id), exp (horloges désynchronisées) ou nonce (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_token arrive mais est rejeté sur exp. 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_token signé. 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 sur 1edtech.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 = 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 ?

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 ?

statesignature (via JWKS, clé du bon kid) → issaud (= mon client_id) → expnonce. 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.

Last updated on