Tutorial: LTI Standard implementieren

Wie schon mehrfach hier im Blog besprochen ist der Ansatz der Schul-Cloud von Grund auf modular. Dies beginnt bei der strikten Trennung zwischen API und Clients. Die API selbst besteht dabei aus vielen kleineren Microservices.

Einer der wichtigsten Microservices ist die Content-Suche, welche selbst auch wieder in kleinere Services unterteilt ist. Ein Teil der kleineren Services dient dabei zum Durchsuchen der Inhalts-Repertoires unserer Partnerunternehmen. Einen weiteren Teil bieten externe, interaktive Tools - wie zum Beispiel bettermarks -, die aus dem Kurs-Kontext heraus genutzt werden können.

Um die Anbindung an solche Tools möglichst einfach zu machen, nutzen wir den Learning Tools Interoperability (kurz LTI) Standard.

Mehr über den Standard und die dahinter liegenden Konzepte lässt sich hier erfahren: https://www.imsglobal.org/activity/learning-tools-interoperability

In dem folgenden Tutorial erklären wir kurz, wie sich dieser Standard einfach in neue und bestehende Projekte integrieren lässt.
Das Tutorial ist grob angelehnt an das englischsprachige PHP Tutorial in der IMS Wiki: http://www.imsglobal.org/wiki/step-1-lti-launch-request

Die im folgenden verwendeten Code-Beispiele finden sich gesammelt und kommentiert in einer Beispiel-Anwendung auf unserem GitHub Account wieder: https://github.com/schul-cloud/node-lti-provider-example/

Voraussetzungen für die LTI Anbindung

In diesem Tutorial setzen wir, genau wie im restlichen Schul-Cloud Projekt, auf NodeJS und MongoDB. Natürlich lässt sich die Logik aber auch in andere Programmiersprachen übertragen.

Als erstes konfigurieren wir eine relativ einfache Node-Anwendung in Kombination mit dem Express-Framework das uns viele Sachen, wie Routing, das Parsen von Requests, etc. abnimmt:

const express = require('express');  
const app = express();

const mongoose = require('mongoose');  
mongoose.connect('mongodb://localhost/lti');

const bodyParser = require('body-parser');  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({extended: true}));

const port = 4000;  
app.listen(port, function() {  
    console.log(`listening on ${port}`);
});

Ablauf einer LTI-Anfrage

1. Validierung

Die Validierung einer LTI-Anfrage besteht aus mehreren Schritten:

Als erstes wird geprüft, um was für eine Art von Anfrage es sich handelt. In den meisten Fällen sollte es sich um einen "Launch Request" handeln, weshalb wir uns im folgenden auf diesen Fall konzentrieren.

Ist ein "Launch Request" eingegangen müssen wir prüfen, ob der Absender des Requests, das Recht hat unsere Anwendung zu starten.
Dies geschieht via OAuth: Dabei erhält der Sender der Anfrage von uns im Vorfeld einen Public und einen Secret Key. Die Keys bestehen einfach aus beliebigen Zeichenkombinationen und können zufällig generiert werden. Wichtig ist nur, dass beide Seiten beide Teile des Keys besitzen.

In der Anfrage enthalten sind der Public Key, eine Signatur und weitere Parameter (siehe unten) - nicht aber der Private Key. Die Signatur setzt sich zusammen aus den Parametern des Requests und wird mir Public und Private Key verschlüsselt.

Eine ziemlich gute Übersicht über benötigte und mögliche Parameter lässt sich hier finden: https://www.edu-apps.org/code.html#building

Da wir auf unserer Seite auch beide Keys haben, können wir nun prüfen ob die Signatur gültig ist und in diesem Fall fortfahren.

Für die komplette Validierung gibt es glücklicherweise ein npm Package, dass uns an dieser Stelle die Arbeit abnimmt: https://github.com/omsmith/ims-lti

Der benötigte Code sieht dann in etwa so aus:

const lti = require('ims-lti');  
const consumerSecret = (consumer || {}).secret;  
const provider = new lti.Provider(consumerKey, consumerSecret);

provider.valid_request(req, (err, isValid) => {  
    if(isValid) {
        /* Do stuff here */
    } else {
        next(err);
    }
});

2. Nutzer-Session anlegen

Nach der erfolgreichen Validierung gilt es nun die empfangenen Daten für die Dauer der Session verfügbar zu machen. Dazu legen wir eine Session an und füllen diese mit den empfangenen Daten.

Für die Session bietet sich express-session in Kombination mit connect-mongo und mongoose an, da wir die Verbindung mit der MongoDB bereits weiter oben hergestellt haben:

const session = require('express-session')  
const MongoStore = require('connect-mongo')(session);

app.use(session({  
    secret: 'SOMETHING VERY SECRET',
    resave: true,
    saveUninitialized: true,
    cookie: {
        secure: false,
        maxAge: 60 * 60 * 24
    },
    store: new MongoStore({
        mongooseConnection: mongoose.connection
    })
}));

Für das Füllen mit Daten prüfen wir zuerst ob bereits eine Session besteht und löschen diese, falls dies der Fall ist. An dieser Stelle kann man jetzt prüfen, ob der Nutzer bereits einen Eintrag in der Datenbank hat und könnte einen anlegen um beispielsweise den Lernfortschritt zu speichern.

Dann eröffnet man eine neue Session und befüllt diese:

req.session.regenerate(err => {  
   if (err) next(err);
   req.session.contextId = provider.context_id;
   req.session.userId = provider.userId;
});

3. Anwendung starten

Ist die Session angelegt können wir jetzt die richtige Anwendung starten.

Beim Starten der Anwendung prüfen wir zuerst ob eine gültige Session besteht und führen sie nur aus, wenn dies der Fall ist:

app.get('/app/', (req, res, next) => {  
    if(req.session.userId) {
        /* You application code */
    } else {
        next('Session invalid. Please login via LTI to use this application.');
    }
});

Um vom Setzen der Session zur Anwendung zu kommen, können wir einfach oben noch eine automatische Weiterleitung ergänzen:

res.redirect(301, '/app/');  

Fertig ist die Integration des LTI Standards. Wie bereits oben erwähnt lässt sich der komplette Code auch nochmal in einer Beispiel-Anwendung auf unserem GitHub Account finden: https://github.com/schul-cloud/node-lti-provider-example/

Falls Sie LTI in Ihr Projekt integriert haben und wollen, dass es aus der Schul-Cloud heraus genutzt werden kann, schreiben Sie uns einfach eine Mail, mit Link zu Ihrer Anwendung.

Newsletter anmelden