DeepLinking not working (LMS hangs when receiving back the deep link response from ltijs)
See original GitHub issueI’ve written a little lti tool client to test out ltijs.
So far I’m able to setup the tool, register the platform and perform a basic launch. So far so good.
The main problem I’m having is in handling deep linking; I want the LMS (Moodle or Canvas) to be able to query my tool for a content selection but I cannot seem able to do that. I see the deep linking flow starting correctly (my lti.onDeepLinking() gets called and I can show a selection page) but when I submit back the deep linking result nothing happens; the LMS seems “hanging there” for a bit and then errors out.
This is my code for the tool:
const path = require("path");
const lti = require("ltijs").Provider;
const ltiKey = "myverylongandspecialltikeyfortesting";
toolUrl = "http://192.168.1.25:3010";
// setup provider
lti.setup(ltiKey, {
url: "mongodb://127.0.0.1:3001/lticlient",
}, {
// options
appRoute: "/lti/launch",
loginRoute: "/lti/login",
cookies: {
secure: false, // Set secure to true if the testing platform is in a different domain and https is being used
sameSite: "None" // set it to "None" if the testing platform is in a different domain and https is being used
},
devMode: true, // set it to false when in production and using https,
keysetRoute: "/lti/keys",
});
// set the lti launch callback
lti.onConnect((token, req, res) => {
console.log("IDTOKEN", token);
return res.send("LTI TOOL LAUNCHED!");
});
lti.onDeepLinking((token, req, res) => {
console.log("DEEP LINKING", token);
// Call redirect function to deep linking view
lti.redirect(res, '/deeplink')
})
// GET request to show the selection page
lti.app.get("/deeplink", async (req, res) => {
res.sendFile(path.join(__dirname, '/public/select.html'))
});
// POST submit from selection page with selected content item
lti.app.post("/deeplink", async (req, res) => {
const resource = req.body
const items = [
{
type: 'ltiResourceLink',
title: resource.product,
url: `${toolUrl}/lti/launch`,
custom: {
product: resource.product
}
}
]
const form = await lti.DeepLinking.createDeepLinkingForm(res.locals.token, items, { message: 'Successfully registered resource!' })
console.log("RETURNING SELF-SUBMITTING FORM", form);
return res.send(form);
})
const getPlatforms = () => {
return [
{
url: "http://192.168.1.239",
name: "MoodleClient1",
clientId: "client-id-provided-by-Moodle",
authenticationEndpoint: "http://192.168.1.239/mod/lti/auth.php",
accesstokenEndpoint: "http://192.168.1.239/mod/lti/token.php",
authConfig: { method: 'JWK_SET', key: "http://192.168.1.239/mod/lti/certs.php" }
}
];
}
const registerPlatforms = async () => {
const platforms = getPlatforms();
platforms.forEach(async (cfg) => {
console.log(`Registering platform ${cfg.name}`);
await lti.deletePlatform(cfg.url, cfg.clientId);
await lti.registerPlatform(cfg);
const platform = await lti.getPlatform(cfg.url, cfg.clientId);
await platform.platformActive(true)
});
}
const setup = async () => {
await lti.deploy({ port: 3010 });
registerPlatforms();
console.log("platforms registered and active");
}
setup();
When I configure the tool in the LMS (Moodle in this case) and I click on the “Select content” button to start the deep linking flow I correctly receive the callback from “onDeepLinking()” and show the selection page:
When I click on the content I want to select, I POST to my own “/deeplink” endpoint (see above in the code snippet) and that generates the self-submitting form that goes back to the LMS (it posts to: http://192.168.1.239/mod/lti/contentitem_return.php?course=2&id=2&sesskey=v3sFBYMDCU passing the encoded JWT param in the form data)
That’s where things end… the moodle selection modal remains white for a while (while the POST is still “pending” in the Network tab in my browser) and then after a bit I get this error:
Any advice on how to fix this @Cvmcosta ? any help would be hugely appreciated; I’ve been stuck with this for a few days now and I’m starting to lose hope
Issue Analytics
- State:
- Created a year ago
- Comments:25
Top GitHub Comments
Hey @phiduo !
So, the good news is that ltijs actually works and it works pretty well! I’m currently using the “Keyset URL” (so, I’m not pasting any RSA public key when configuring the tool into the LMS).
But first, a bit of context:
What you’re doing here is to configure “your” application as a “LMS Tool Provider” (that is, the application you’re building is the one “providing” some external functionality to the LMS - Moodle - you’re integrating with). The LMS in this context acts as “Tool consumer” (or as we refer to, as a “Platform”).
The steps you follow when using LTIJS as a library are:
lti.setup()
function call. When doing this, you also provide a “SIGN KEY” that is nothing more than a sequence of characters that the LTIJS library will use to sign any packet exchanged with the LMS platform (my guess is that this key will be used as a seed when internally generating the private and public keys for your tool, something LTIJS takes care of on your behalf). Example:What about the URLS? (/lti/launch, /lti/login, /lti/keys)
When you call the lti.deploy() function, LTIJS will automatically listen for incoming launch requests; the URLs for which LTIJS accepts incoming requests are the ones you specified in the lti.setup() call (appRoute, loginRoute, keysetRoute). You can actually choose whatever path you like for those URLs (personally I like keeping all of them under a common path, like “/lti/…”). Those path will then be used by LTIJS as URL endpoints for the complex IMS launch protocol and messages exchange to take place.
RSA Keys or RSA URL?
When configuring your tool in the LMS (Administration -> Plugins -> External Tool configuration) you can either specify a KeySet URL or an RSA Key. The main difference is that if you cofigure an RSA Key it’s your responsibility (as a developer) to provide that information to the LMS Administrator; Using the Keyset URL is really the best option, since that URL is automatically queried by the LMS whenever it needs to decode any incoming message from your tool (like the Deep Linking response you send back to them, if you use that functionality).
If you use the Keyset URL (recommended!), just provide the URL you decided and put into your lti.setup() function call (the value you used for “keysetRoute”). That URL needs to be reachable from the LMS and you can easily check that this is the case: if your tool is running on http://192.168.1.99:3000, then you can just open a new tab in the browser and hit http://192.168.1.99:3000/lti/keys and you should see the JSON response with the public key(s) that LTIJS has created for you. This is the URL the LMS will hit to retrieve them too.
If you use the RSA Key method instead, you need to put the key corresponding to the platform you’re using (from what I’ve seen the correct one to add there is the JSON one you get from hitting the /lti/keys endpoint on your tool Express server). There should be no reason for you to use this method though: the Keyset URL should just work; if it doesn’t you’re likely having issues at network level on your side (that was my case, because of my damn Firewall!)
Fixing Bitnami’s VM
That took a bit, since I’m unfamiliar with Debian distros (I’m on Arch). I’m on holidays right now, so I don’t have with me the VM but if I remember correctly, these are the steps I followed:
Hopefully this should work. If for some reason it doesn’t, just google “timedatecl Debian (or Ubuntu)” and I think you’ll find info on how to use it. As a last resort, you can also run sudo timedatectl set-time <my time> to “manually” sync the VM to your own time.
Hope this helps!
@dmolin Glad to hear that you solved it! 👍