
Wenn du je versucht hast, eine Integration für ein Learning-Management-System (LMS) wie Canvas, Moodle oder Blackboard zu bauen, kennst du den Schmerz. Genauer gesagt den Schmerz von LTI 1.3 (Learning Tools Interoperability).
Es beinhaltet einen komplexen Tanz aus OIDC-Logins, JWKS-Keysets, RSA-Signaturen und privaten Schlüsseln. Es soll sicher sein, aber für einen Entwickler, der einfach nur "eine Web-App in Canvas einbetten" will, kann es sich wie ein Albtraum anfühlen.
Das ist die Architektur, bei der ich gelandet bin: Node.js für die Sicherheitsschicht, Next.js für das Frontend, bewusst getrennt statt als ein Monolith gebaut.
🔗 GitHub Repository: github.com/paurushrai/lti-1.3-node-nextjs-template
🧐 Was ist LTI 1.3, und warum sollte es dich interessieren?
LTI (Learning Tools Interoperability) ist der Standard, der externen Tools (wie deiner App) erlaubt, mit einem LMS (wie Canvas) zu kommunizieren.
Früher war LTI 1.1 einfach: man teilte einen "Secret Key" und einen "Consumer Key". Aber es war unsicher. LTI 1.3 wechselt zum modernen Informationssicherheitsmodell:
- OIDC (OpenID Connect) für den Login-Flow.
- JWTs (JSON Web Tokens) für den Datenaustausch.
- Public/Private-Key-Verschlüsselung (RSA256) statt geteilter Secrets.
Das macht es unglaublich sicher, aber auch schwerer, von Grund auf zu implementieren.
🏗 Die Architektur
Um das zu lösen, bauen wir keinen Monolithen. Wir trennen unseren Security Provider von unserer Anwendungs-UI.
Der Tech-Stack
- Backend (der Sicherheitsdienst): Node.js + Express +
ltijs(die Standard-Node.js-Bibliothek für LTI 1.3, von Cvmcosta). - Frontend (das Erlebnis): Next.js 14+ (App Router) + React + Tailwind CSS.
- Datenbank: PostgreSQL (speichert kryptografische Schlüssel und Plattform-Registrierungen).
- Infrastruktur: Docker Compose.
Das Problem: die "Third-Party-Cookie"-Apokalypse
Eine große Herausforderung bei LTI-Tools ist, dass sie innerhalb eines <iframe> im LMS laufen. Moderne Browser (Safari, Chrome Inkognito) blockieren Third-Party-Cookies standardmäßig.
Wenn sich deine Web-App auf ein Session-Cookie (connect.sid) verlässt? Sie wird in Safari scheitern.
Die Lösung: der "LTIaaS"-Token-Flow
Dieses Projekt implementiert ein "zustandsloses" Token-Muster, um die Cookie-Einschränkung zu umgehen:
- LMS-Launch: Das LMS sendet eine Anfrage an unser Node.js-Backend.
- Handshake: Das Backend verifiziert die Signatur. Wenn gültig, erzeugt es ein kurzlebiges, einmal nutzbares ltik (LTI Key).
- Redirect: Das Backend leitet das iframe zum Next.js-Frontend um und übergibt den Token in der URL:
https://myapp.com/app?ltik=eyJhb... - Hydration: Die Next.js-App holt diesen Token aus der URL.
- API-Aufrufe: Das Frontend nutzt diesen Token als Bearer-Token im
Authorization-Header für alle zukünftigen API-Aufrufe.
Keine Cookies nötig!
🚀 So richtest du das ein (Schritt für Schritt)
So bringst du das in fünf Minuten auf deinem lokalen Rechner zum Laufen.
1. Voraussetzungen
Du brauchst Docker installiert. Du brauchst außerdem ngrok (oder einen ähnlichen Tunneling-Dienst), weil LTI-Tools zwingend über HTTPS ausgeliefert werden müssen. Du kannst nicht einfach localhost verwenden.
2. Das Repository klonen
git clone https://github.com/paurushrai/lti-1.3-node-nextjs-template.git
cd lti-1.3-node-nextjs-template3. Umgebung konfigurieren
Kopiere die Beispiel-Umgebungsdatei:
cp lti-server/.env.example lti-server/.envErzeuge einen zufälligen, sicheren Schlüssel und füge ihn in LTI_KEY in der .env ein.
4. Den Tunnel starten
Starte ngrok auf Port 3000 (dort läuft unser Node-Server).
ngrok http 3000Kopiere deine https-URL (z. B. https://my-tool.ngrok-free.app).
5. Die App starten
Öffne lti-server/.env erneut und setze UI_URL auf deine ngrok-URL.
Jetzt startest du alles mit Docker:
docker-compose up --buildDu hast jetzt:
- LTI-Server:
http://localhost:4000 - Next.js-Client:
http://localhost:3000 - Postgres: automatisch verbunden.
🔌 Mit dem LMS verbinden (der "Magie"-Link)
Früher musstest du öffentliche Schlüssel und URLs manuell copy-pasten. Dieses Projekt unterstützt LTI Dynamic Registration.
- Kopiere deine Registrierungs-URL:
https://<your-ngrok-domain>/lti/register - Gehe zu deinem LMS (z. B. Canvas Admin → Developer Keys).
- Wähle "Dynamic Registration".
- Füge die URL ein und klicke auf Register.
Bumm. 💥 Das Tool und das LMS tauschen automatisch öffentliche Schlüssel aus. Das Tool ist jetzt installiert.
🧪 Den Flow testen
- Erstelle eine generische Aufgabe oder ein Modul-Element in deinem LMS-Kurs.
- Wähle "External Tool" und dein neues Tool aus.
- Starte es.
- Du siehst die Next.js-App im iframe laden.
- Sie zeigt: "Welcome, [Your Name]" und deine Rolle (Instructor/Student).
Die App hat erfolgreich:
- Den Launch authentifiziert.
- Dein Nutzerprofil aus den LTI-Launch-Daten dekodiert.
- Es sicher an das Frontend übergeben.
Würdest du die Sicherheitsschicht genauso von der UI trennen?
Die Protokolllogik (Node/ltijs) von der Anwendungslogik (Next.js) zu trennen, hat das Ganze überhaupt erst handhabbar gemacht: Die Sicherheitsschicht bleibt langweilig und auditierbar, und die UI bleibt eine normale Next.js-App, die nichts über OIDC oder JWKS wissen muss.
Der Code ist auf GitHub, wenn du ihn forken und von etwas ausgehen willst, das den Handshake schon erledigt: lti-1.3-node-nextjs-template. Ich bin neugierig, ob schon mal jemand einen Fall hatte, in dem diese Trennung der beiden Schichten eher im Weg stand als geholfen hat.


