Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DeepLinking not working (LMS hangs when receiving back the deep link response from ltijs)

See original GitHub issue

I’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 = "";

// setup provider
lti.setup(ltiKey, {
  url: "mongodb://",
}, {
  // 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"/deeplink", async (req, res) => {
  res.sendFile(path.join(__dirname, '/public/select.html'))

// POST submit from selection page with selected content item"/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: "",
      name: "MoodleClient1",
      clientId: "client-id-provided-by-Moodle",
      authenticationEndpoint: "",
      accesstokenEndpoint: "",
      authConfig: { method: 'JWK_SET', key: "" }

const registerPlatforms = async () => {
  const platforms = getPlatforms();
  platforms.forEach(async (cfg) => {
    console.log(`Registering platform ${}`);
    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 });
  console.log("platforms registered and active");

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: 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:closed
  • Created a year ago
  • Comments:25

github_iconTop GitHub Comments

dmolincommented, Sep 1, 2022

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:

  • configure the LTIJS library: this is what you do with the 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:
 lti.setup("myverysecretkey000", {
    url: "DB-Connection-URL"
  }, {
    // options
    appRoute: "/lti/launch",
    loginRoute: "/lti/login",
    keysetRoute: "/lti/keys",
    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,
  • register one or more LMS platforms in your LTIJS library. This “configures” LTIJS so it can recognize launch requests coming from those platforms; for each platform LTIJS will automatically generate a private and public key (using your signkey as a seed I guess).
  • listen for incoming connections from the LMS platforms you’ve registered This is what you do when calling lti.deploy({ port: 3000 }). LTIJS actually sets up an Express server listening on that specific port for “incoming” launch requests from the LMS platforms you’ve configured.

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, then you can just open a new tab in the browser and hit 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:

  • update your system: sudo apt-get update -y
  • Install DBUS: sudo apt-get install dbus
  • ensure you’ve the timedatectl command installed (you should be able to run it with sudo)
  • reboot
  • install systemd-timesyncd: sudo apt-get install systemd-timesyncd
  • enable systemd-timesyncd service: sudo systemctl enable systemd-timesyncd.service (maybe reboot after that too)
  • now you should be able to see some results with timedatectl: sudo timedatectl list-timezones
  • set your timezone: an example: sudo timedatectl set-timezone Europe/London
  • enable the NTP network time service: sudo timedatectl set-ntp on
  • shutdown again

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!

johnnyoshikacommented, Aug 3, 2022

@dmolin Glad to hear that you solved it! 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

ltijs: Deep Linking hanging when sending back the data to the ...
I'm loosing my sleep trying to make ltijs work with Deep Linking. I've built a simple client that registers a tool against an...
Read more >
LTI 1.3 Deep Linking Response - Unable to process
I've installed Canvas and is running it locally with Docker (on stable branch ). I'm currently experimenting with the assignment selection.
Read more >
Part 3: Troubleshooting your deep links - YouTube
In this video we'll use the command line tools and the Android debug bridge to diagnose and debug common issues with Deep Links....
Read more >
LTI deep linking adding multiple content items request hangs
I have developed an LTI 1.1 deep linking tool that returns multiple gradable content items. I am testing on our AWS Bitnami LMS...
Read more >
Third International Computer Programming Education ...
Feedback Systems for Students Solving Problems Related to Polynomials of ... method and how it was applied to construct a new and deeper...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Post

No results found

github_iconTop Related Hashnode Post

No results found