Skip to Content

Chapitre 9.3 — LTI 1.3 : OIDC + JWT

⏱️ TL;DRLTI 1.3 remplace le secret partagé de 1.1 par de la cryptographie asymétrique. La plateforme signe le lancement avec sa clé privée ; l’outil vérifie avec la clé publique correspondante, récupérée dans le JWKS (un jeu de clés publié à une URL). Le lancement lui-même est un JWT (le id_token), un jeton signé qui transporte, dans ses claims, l’identité, le contexte et le type de message. Le tout s’appuie sur OpenID Connect (OIDC), avec une amorce de login qui garantit qu’on parle bien à la bonne plateforme. Comprendre JWT + JWKS + OIDC, c’est comprendre 90 % de LTI 1.3.

🎯 Objectifs

  • Comprendre pourquoi la cryptographie asymétrique supprime le problème du secret partagé.
  • Lire la structure d’un JWT (header, payload, signature) et savoir ce qu’est le id_token.
  • Comprendre le rôle du JWKS et de la rotation de clés.
  • Situer OpenID Connect comme socle du lancement, et pourquoi l’amorce de login existe.

Asymétrique : la fin du secret partagé

Le défaut de LTI 1.1 était le secret symétrique : la même valeur des deux côtés. LTI 1.3 passe à l’asymétrique — une paire de clés :

  • une clé privée, que le signataire garde secrète et ne partage jamais ;
  • une clé publique, dérivée de la privée, qu’on peut diffuser librement.

Ce qui est signé avec la clé privée se vérifie avec la clé publique correspondante — et seulement elle. Conséquence directe : la plateforme signe le lancement avec sa clé privée ; l’outil n’a besoin que de la clé publique de la plateforme pour vérifier. Il n’existe donc aucun secret commun à distribuer ni à protéger des deux côtés. Une clé publique peut fuiter sans conséquence : elle ne permet pas de forger une signature.

LTI 1.1 (symétrique)LTI 1.3 (asymétrique)
Ce que détient la plateformeLe secret partagéSa clé privée
Ce que détient l’outilLe même secretLa clé publique de la plateforme
Fuite côté outilCompromet toutSans conséquence (clé publique)
Distribution de secretObligatoire, par intégrationAucune

💡 Réflexe — Retiens le sens : la plateforme signe (clé privée), l’outil vérifie (clé publique). Quand ton outil aura aussi besoin d’appeler la plateforme (renvoyer une note en Partie 10), les rôles s’inversent : c’est alors ton outil qui signe avec sa clé privée, et la plateforme qui vérifie avec ta clé publique. Chaque partie a donc sa paire de clés.

Le JWT : un jeton signé qui parle

Le lancement 1.3 voyage sous la forme d’un JWT (JSON Web Token) appelé id_token. Un JWT, c’est trois parties encodées en Base64URL, séparées par des points : header.payload.signature.

// 1) header — l'algorithme et l'identifiant de clé (kid) { "alg": "RS256", "typ": "JWT", "kid": "platform-key-2026-01" }
// 2) payload — les "claims" : ici, un lancement LTI (extrait) { "iss": "https://moodle.ecole.fr", "aud": "formacampus-studio-client-id", "sub": "u-2342", "exp": 1893456000, "iat": 1893455940, "nonce": "n-8f3a1c", "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/roles": [ "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner" ] }

La troisième partie, la signature, est calculée sur header.payload avec la clé privée de la plateforme (algorithme RS256, par exemple). L’outil la vérifie avec la clé publique. Les claims standard OIDC portent l’essentiel de la sécurité :

  • iss (issuer) : qui émet le jeton (la plateforme). Doit correspondre à la plateforme enregistrée.
  • aud (audience) : pour qui — le client_id de ton outil. Si ce n’est pas toi, rejette.
  • exp (expiration) et iat (issued at) : le jeton est éphémère ; un jeton expiré est refusé.
  • nonce : une valeur à usage unique qui empêche le rejeu d’un lancement capté.
  • sub (subject) : l’identifiant de l’utilisateur chez la plateforme.

⚠️ Piège — Lire les claims sans vérifier la signature ni les champs aud, exp, nonce. Un JWT est lisible par tous (Base64, pas chiffré) : sa valeur de sécurité vient entièrement de la vérification de la signature et des claims. « Décoder » un JWT n’est pas « le valider ». Un outil qui affiche le contenu d’un id_token non vérifié accorde sa confiance à n’importe qui.

Le JWKS : où trouver la clé publique

Comment ton outil obtient-il la clé publique de la plateforme pour vérifier la signature ? Via le JWKS (JSON Web Key Set) : un document JSON, publié par la plateforme à une URL (le jwks_uri), qui liste ses clés publiques.

// Exemple de JWKS (simplifie) { "keys": [ { "kty": "RSA", "kid": "platform-key-2026-01", "use": "sig", "alg": "RS256", "n": "0vx7agoebGcQSuu...base64url...", "e": "AQAB" } ] }

Le lien se fait par le kid (key id) : le header du JWT contient un kid, l’outil cherche la clé du même kid dans le JWKS de la plateforme, et l’utilise pour vérifier la signature. Cet indirection permet la rotation : la plateforme peut publier une nouvelle clé (nouveau kid) et retirer l’ancienne sans rien te transmettre — ton outil relit le JWKS et suit. C’est pour ça qu’on stocke l’URL du JWKS, pas la clé elle-même.

💡 RéflexeNe code jamais une clé publique en dur. Stocke le jwks_uri de la plateforme et récupère les clés (avec un cache raisonnable). Ainsi, quand la plateforme fait tourner ses clés, ton intégration continue de marcher sans intervention. Coder une clé en dur, c’est se garantir une panne au prochain renouvellement.

OpenID Connect et l’amorce de login

Un dernier ingrédient : OpenID Connect (OIDC), la couche d’identité au-dessus d’OAuth 2.0 sur laquelle LTI 1.3 s’appuie. En particulier, LTI 1.3 utilise un login third-party initiated : avant le lancement proprement dit, la plateforme envoie à ton outil une amorce de login (OIDC login initiation). Pourquoi cette étape en plus ?

  • Elle permet à ton outil de savoir de quelle plateforme vient la demande (le claim iss), donc quelle configuration utiliser si tu sers plusieurs plateformes.
  • Elle établit un aller-retour anti-CSRF (via un state) et un nonce anti-rejeu avant de recevoir le jeton.
  • Elle garantit que le jeton final est demandé par ton outil à la plateforme, et non poussé aveuglément.

Le déroulé complet — amorce, requête d’autorisation, réception et validation du id_token — fait l’objet du chapitre suivant. Ici, retiens les briques : asymétrique (fin du secret partagé), JWT (id_token signé, à valider), JWKS (clés publiques par kid, rotation possible), OIDC (amorce de login, state, nonce).

🔌 Côté intégration — Ces briques (OIDC, JWT, JWKS) ne sont pas propres au e-learning : ce sont les standards du web moderne d’authentification. Si tu as déjà intégré « Se connecter avec Google » ou un SSO d’entreprise, tu connais déjà le terrain. C’est une bonne nouvelle : LTI 1.3 réutilise des compétences transférables, et des bibliothèques matures existent pour valider un JWT et lire un JWKS dans tous les langages.

🧭 Sur FormaCampus — Le studio FormaCampus (l’outil) génère sa paire de clés et publie son JWKS à une URL stable. Pour chaque école cliente (chaque plateforme), il enregistre le jwks_uri de son Moodle. Résultat : à chaque lancement, le studio récupère la bonne clé publique par kid, valide la signature du id_token, contrôle aud (= son client_id), exp et nonce, puis seulement alors fait confiance à l’identité et au rôle transmis. Zéro secret partagé, rotation de clés indolore.

📚 La spec — LTI 1.3 Core (1EdTech, 1edtech.org) s’appuie sur OpenID Connect, JWT (RFC 7519), JWS pour la signature et JWK/JWKS (RFC 7517) pour les clés. Les claims LTI sont namespacés sous https://purl.imsglobal.org/spec/lti/claim/. On les détaille au chapitre 9.5.

✏️ Exercices

Exercice 1 — Qui signe, qui vérifie ? Au lancement (la plateforme lance ton outil), quelle clé signe le id_token, et quelle clé le vérifie ? Et quand, plus tard, ton outil renvoie une note à la plateforme, qui signe et qui vérifie ?

✅ Solution

Au lancement : la plateforme signe le id_token avec sa clé privée ; ton outil vérifie avec la clé publique de la plateforme (récupérée dans son JWKS). Quand ton outil appelle la plateforme (renvoi de note, Partie 10) : ton outil signe sa requête d’accès avec sa clé privée ; la plateforme vérifie avec ta clé publique (via ton JWKS). Chaque partie a sa paire de clés ; le sens « qui signe / qui vérifie » dépend de qui appelle qui.

Exercice 2 — Décoder n’est pas valider. Un dev affiche fièrement le contenu d’un id_token qu’il a « décodé » et en conclut « l’utilisateur est bien enseignant ». Où est la faille ?

✅ Solution

Un JWT est encodé en Base64URL, pas chiffré : n’importe qui peut le décoder et lire les claims — y compris un attaquant qui fabrique un faux jeton disant « roles: Instructor ». La sécurité vient de la vérification de la signature (via le JWKS de la plateforme) et des claims aud (= mon client_id), exp (non expiré) et nonce (non rejoué). Décoder ≠ valider. Tant que la signature et ces claims ne sont pas vérifiés, le contenu ne prouve rien.

🧠 Quiz de révision

1. Quel problème de LTI 1.1 la cryptographie asymétrique résout-elle ?

Le secret partagé. En asymétrique, la plateforme signe avec sa clé privée (jamais partagée) et l’outil vérifie avec la clé publique. Aucun secret commun à distribuer ; la fuite d’une clé publique est sans conséquence.

2. Qu’est-ce que le id_token en LTI 1.3 ?

Le lancement lui-même, sous forme de JWT signé : header.payload.signature. Son payload contient les claims (identité, contexte, type de message, plus iss, aud, exp, nonce).

3. À quoi sert le JWKS, et pourquoi stocker son URL plutôt que la clé ?

Le JWKS publie les clés publiques de la plateforme, identifiées par kid. Stocker le jwks_uri (et non la clé) permet de suivre la rotation des clés sans intervention : l’outil relit le JWKS et trouve la nouvelle clé par son kid.

4. Que doit vérifier l’outil sur le id_token avant d’y faire confiance ?

La signature (via le JWKS, clé du bon kid), puis les claims : iss (plateforme attendue), aud (= son client_id), exp (non expiré), nonce (non rejoué). Sans ces contrôles, le contenu n’a aucune valeur probante.

5. Pourquoi LTI 1.3 ajoute-t-il une amorce de login OIDC ?

Pour identifier quelle plateforme initie (claim iss, utile si l’outil en sert plusieurs), établir un aller-retour anti-CSRF (state) et anti-rejeu (nonce), et garantir que le jeton est demandé par l’outil, non poussé aveuglément.


Chapitre suivant : Le flux de lancement — les étapes exactes, du clic de l’élève à l’affichage validé de l’outil.

Last updated on