Cvmcosta / ltijs

Turn your application into a fully integratable LTI 1.3 tool provider.
https://cvmcosta.github.io/ltijs/
Apache License 2.0
299 stars 67 forks source link

"NO_LTIK_OR_IDTOKEN_FOUND" error using ltijs-demo-server with Moodle as an external tool #95

Closed vandergav closed 3 years ago

vandergav commented 3 years ago

Hi Carlos,

Can I get some help on setting up the ltijs-demo-server as an external tool for Moodle?

I have the ltijs-demo-server running locally on port 8100 with the following console logs on npm start: image I then configured a Demo tool with the following configurations: image However, when I launch the tool, I get the following error in Moodle: image Not sure if this helps but here are the console logs when I launch the tool: image

I'm new to the LTI protocol so pardon me if I have made silly mistakes here. Thanks for the help in advance!

Cvmcosta commented 3 years ago

Hello @vandergav ! The Initiate login URL on moodle should be http://localhost:8001/login. And the Redirection URI(s) should be http://localhost:8001

vandergav commented 3 years ago

Thank you so much Carlos for your quick reply :D I have implemented your changes and somehow, the error has changed to {"status":401,"error":"Unauthorized","details":{"description":"Error validating ltik or IdToken","message":"AUTHCONFIG_NOT_FOUND"}} I see that I specified authConfig field here: image and looking up https://localhost/moodle/mod/lti/certs.php in the browser gives me this: image so I am not sure if I am missing something here.

Thank you so much in advance!

Cvmcosta commented 3 years ago

That is weird, this error only happens when the authConfig.method is not set or set to an invalid method. Your registration seems correct. Although i noticed that you are receiving a launch request from https://localhost/moodle but you registered http://localhost/moodle, that might have something to do with it.

You could also try printing the Auth Config after the registration so we can check if it's being correctly set (Maybe do that after changing the platform url):

const platform = lti.registerPlatform(...)
const authConfig = await platform.platformAuthConfig()
console.log(authConfig)
vandergav commented 3 years ago

Alright, I have changed the setup function following your comments: image Still got the same errors so I added the platform.platformAuthConfig() code and here are the console logs: image Still getting the same AUTHCONFIG_NOT_FOUND error.

Here are screenshots from the chrome inspector: login image https://localhost/moodle/mod/lti/auth.php?response_type=id_token&response_mode=form_post&id_token_signed_response_alg=RS256&scope=openid&client_id=6eKXDwkQF8raQE8&redirect_uri=http%3A%2F%2Flocalhost%3A8100&login_hint=2&nonce=sbvxofscvel7qbvthdcednwgp&prompt=none&state=83eb75baa97b616bf8f8c96f4b14e3dae03950b7d80df30944&lti_message_hint=3&lti_deployment_id=4 image localhost image I decoded the jwt here: { "nonce": "sbvxofscvel7qbvthdcednwgp", "iat": 1617984613, "exp": 1617984673, "iss": "https://localhost/moodle", "aud": "6eKXDwkQF8raQE8", "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "4", "https://purl.imsglobal.org/spec/lti/claim/target_link_uri": "http://localhost:8100", "sub": "2", "https://purl.imsglobal.org/spec/lti/claim/lis": { "person_sourcedid": "", "course_section_sourcedid": "" }, "https://purl.imsglobal.org/spec/lti/claim/roles": [ "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator", "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor", "http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator" ], "https://purl.imsglobal.org/spec/lti/claim/context": { "id": "2", "label": "GESL", "title": "Group Endeavours in Service Learning", "type": [ "CourseSection" ] }, "https://purl.imsglobal.org/spec/lti/claim/resource_link": { "title": "Demo", "id": "2" }, "https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome": { "lis_result_sourcedid": "{\"data\":{\"instanceid\":\"2\",\"userid\":\"2\",\"typeid\":\"4\",\"launchid\":1764766087},\"hash\":\"1faa09052af2f9293ee90dc77b55edd7b39a84e6877a3235a9c42d98b5eddf2c\"}", "lis_outcome_service_url": "https://localhost/moodle/mod/lti/service.php" }, "given_name": "Admin", "family_name": "User", "name": "Admin User", "https://purl.imsglobal.org/spec/lti/claim/ext": { "user_username": "titeadmin", "lms": "moodle-2" }, "email": "help.tite@gmail.com", "https://purl.imsglobal.org/spec/lti/claim/launch_presentation": { "locale": "en", "document_target": "iframe", "return_url": "https://localhost/moodle/mod/lti/return.php?course=2&launch_container=3&instanceid=2&sesskey=9jbVSAL2K4" }, "https://purl.imsglobal.org/spec/lti/claim/tool_platform": { "product_family_code": "moodle", "version": "2020110902", "guid": "d3321a573628b95a63612317ff36fc2c", "name": "Teamwork Intelligence for Tertiary Learners", "description": "TITE" }, "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0", "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiResourceLinkRequest" }

P.S. Sorry for the really long post

Cvmcosta commented 3 years ago

I am guessing something must have gone wrong during the registration, but calling registerPlatform again should have fixed any previous issues.

Please try adding the following code right after printing authConfig:

switch (authConfig.method) {
  case 'JWK_SET':
  {
    console.log(authConfig.method)
    break
  }
  case 'JWK_KEY':
  {
    console.log(authConfig.method)
    break
  }
  case 'RSA_KEY':
  {
    console.log(authConfig.method)
    break
  }
  default:
  {
    console.log('No auth configuration found for platform')
  }
}

This is similar to what happens in the backend. You could also try deleting the platform with platform.delete() and then creating a new registration.

vandergav commented 3 years ago

Okay, I have added the code you gave and also ran platform.delete() to reset everything. I did the following:

  1. ran setup() with platform.delete()
  2. ran setup() without platform.delete() and got the following { method: 'JWK_SET', key: 'https://localhost/moodle/mod/lti/certs.php' } <-- checked that this is of type Object using typeof authConfig JWK_SET
  3. Stopped the server and ran npm start again {"method":"JWK_SET","key":"https://localhost/moodle/mod/lti/certs.php"} <-- checked that this is of type String using typeof authConfig No auth configuration found for platform
  4. It seems like only the first registration after a platform.delete() will give the a type Object authConfig, subsequent restarting of the server without a platform.delete() will give a String authConfig Heres my code for your reference: image Again, can't thank you enough for helping me with this!
Cvmcosta commented 3 years ago

So the platformAuthConfig is returning a String? is that it? What version of Ltijs are you using? And are you using any Database plugins?

vandergav commented 3 years ago

Yes platformAuthConfig returned an object of type String. Yes I am using the ltijs-sequelize Database plugin. Here are the version numbers

"ltijs": "^5.6.5", "ltijs-sequelize": "^2.4.1"

Cvmcosta commented 3 years ago

Okay, thanks! I will look into it today. Never ran into this issue. I'll see if i can reproduce it and identify the cause.

In the meantime, could you try to run Ltijs with the default MongoDB database adapter, this way we can know if the issue is Ltijs or the sequelize library.

vandergav commented 3 years ago

Thank you so much for your help. Heres my code from index.js. Yes, will try out with the default mongoDB if time permits.

require('dotenv').config()
const path = require('path')
const routes = require('./src/routes')

const lti = require('ltijs').Provider
const Database = require('ltijs-sequelize')

// Setup ltijs-sequelize using the same arguments as Sequelize's generic contructor
const db = new Database(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS,  
  { 
    host: process.env.DB_HOST,
    dialect: 'mysql',
    logging: false 
  })

// Setup
lti.setup(process.env.LTI_KEY,
  { 
    plugin: db // Passing db object to plugin field
  }, 
  {
    staticPath: path.join(__dirname, './public'), // Path to static files
    cookies: {
      secure: false, // Set secure to true if the testing platform is in a different domain and https is being used
      sameSite: '' // Set sameSite to 'None' if the testing platform is in a different domain and https is being used
    },
    devMode: true ,// Set DevMode to true if the testing platform is in a different domain and https is not being used
    // dynRegRoute: '/register', // Setting up dynamic registration route. Defaults to '/register'
    // dynReg: {
    //   url: 'http://localhost:8100', // Tool Provider URL. Required field.
    //   name: 'Tool Provider', // Tool Provider name. Required field.
    //   // logo: 'http://localhost:8100/assets/logo.svg', // Tool Provider logo URL.
    //   description: 'Tool Description', // Tool Provider description.
    //   redirectUris: ['http://localhost:8100/launch'], // Additional redirection URLs. The main URL is added by default.
    //   customParameters: { key: 'value' }, // Custom parameters.
    //   autoActivate: false // Whether or not dynamically registered Platforms should be automatically activated. Defaults to false.
    // }
  })

// When receiving successful LTI launch redirects to app
lti.onConnect(async (token, req, res) => {
  return res.sendFile(path.join(__dirname, './public/index.html'))
})

// When receiving deep linking request redirects to deep screen
lti.onDeepLinking(async (token, req, res) => {
  return lti.redirect(res, '/deeplink', { newResource: true })
})

// Setting up routes
lti.app.use(routes)

// Setup function
const setup = async () => {
  await lti.deploy({ port: 8100 })

  /**
   * Register platform
   */

  const platform = await lti.registerPlatform({
    url: 'https://localhost/moodle',
    name: 'Platform',
    clientId: '3ZoyoxJ4NJNZ8Y4',
    authenticationEndpoint: 'https://localhost/moodle/mod/lti/auth.php',
    accesstokenEndpoint: 'https://localhost/moodle/mod/lti/token.php',
    authConfig: { method: 'JWK_SET', key: 'https://localhost/moodle/mod/lti/certs.php' }
  }) 
  const authConfig = await platform.platformAuthConfig()
  console.log(authConfig)
  // platform.delete()
  switch (authConfig.method) {
    case 'JWK_SET':
    {
      console.log(authConfig.method)
      break
    }
    case 'JWK_KEY':
    {
      console.log(authConfig.method)
      break
    }
    case 'RSA_KEY':
    {
      console.log(authConfig.method)
      break
    }
    default:
    {
      console.log('No auth configuration found for platform')
    }
  }
}

setup()
Cvmcosta commented 3 years ago

Hello @vandergav ! I found the issue, apparently it is an issue with the mysql dialect of the sequelize library. Can you please look through this thread and tell me if your environment fits some of the descriptions provided? This seems to be a common issue with mysql sequelize.

vandergav commented 3 years ago

Hi Carlos, thank you for pointing me to the sequelize library. I realised that I actually have a mariaDB database so changing it to dialect: 'mariadb' solved the String issue. However now I am getting another error, {"status":401,"error":"Unauthorized","details":{"description":"Error validating ltik or IdToken","message":"self signed certificate"}} I am wondering if it is because I have an invalid SSL certificate: image

Update: I changed some configuration settings for Moodle's external tool but still got back the same error image

Cvmcosta commented 3 years ago

It might be. Since you are running thing locally you could try without https.

vandergav commented 3 years ago

Thank you, as I have some difficulty doing that, I switched the authConfig to using RSA_KEY instead: image and got these console logs: image Strange how console.log(authConfig) and console.log(await platform.platformPublicKey()) are showing different keys (I have tried deleting the platform and re-registering but still got the same logs)

In Moodle, the error changed to: {"status":401,"error":"Unauthorized","details":{"description":"Error validating ltik or IdToken","message":"invalid signature"}}

I'll continue to work on this but thank you very much for your help so far

Cvmcosta commented 3 years ago

Where did you get this RSA Key from? The authConfig key has to be the Moodle URL you were using before.

The key you get by calling await platform.platformPublicKey() is generated by Ltijs and is used by Moodle to validate your request (That is the key you use in the Moodle Tool registration form). The key you set using authConfig is the URL or Key Ltijs is uses to validate Moodle's requests. They are different things.

I think your best chance is to use both Platform and Tool without https, since this will get rid of the other error in this local environment.

vandergav commented 3 years ago

I got the RSA Key from here: https://lti-ri.imsglobal.org/keygen/index And thank you for your explanation on the different keys! Alright, I will try using the Platform and Tool without the https. Can I check if this means I change the external tool settings? Or the index.js file of the demo server? Or both? Sorry I'm really new to this.

Cvmcosta commented 3 years ago

In the Moodle External tool registration form, change the Public Key Type field to Keyset URL and enter the following URL in the Public Keyset Field: http://localhost:8001/keys, this way you don't have to worry about the keys anymore, Ltijs will handle everything.

Sorry i hadn't noticed you had used moodle's own keyset endpoint in the Public Keyset field, that was probably what caused the self signed certificate issue and maybe not the https. Try the solution above, if it still throws an issue then you can change the Platform registration URLs to http.

Remember that the authConfig.key should be the Moodle certs.php endpoint

The Public keyset you enter on Moodle is the keyset Moodle will use to validate the Tool's requests, so we use http://localhost:8001/keys. The authConfig.key used when registering the Platform is the keyset used by the Tool to validate Moodle's requests, so we use https://localhost/moodle/mod/lti/certs.php

vandergav commented 3 years ago

Thanks so much for the quick reply! I have made the changes to the Public Keyset field but still got the same error so I changed the Platform registration URLs to http too, here are the console logs from console.log(await platform.platformJSON()): image However, I still got back the same error, {"status":401,"error":"Unauthorized","details":{"description":"Error validating ltik or IdToken","message":"self signed certificate"}} I realise when I enter http://localhost/moodle/mod/lti/certs.php into the browser, it redirects to https://localhost/moodle/my/ On the other hand, https://localhost/moodle/mod/lti/certs.php shows

 {
    "keys": [
        {
            "kty": "RSA",
            "alg": "RS256",
            "kid": "24cc901627fc46a01a7d",
            "e": "AQAB",
            "n": "qwlib0E3lABcVSvIond5dJlaDs0zSTPYMHRgqRQ5KnPOsCWf4uOgEm5pKivuUezb7Y1hH-AQ3R_wQ2r_d72r0FpUEVki8qYwp9LeBrZ9uCvngnecrE9F6T0VYOr2FcP6l1jwaW25IdYgxApn5qxY8DW6_xUZfxg0--8dmcqejGDzpYCYFZeeuxeccyC7RU1iPbJmpsq-qTmWqzaL89wHHykIhruf6nm0PRY_C3FSl_2X6taaMc53tYcRkUi_e6g0g2MmqAGAUjpCB2tp6FtO5JQA-XRgX_E2m25tqFHTGsNs3EYoxsW6LyS6jCqXEQJzy5OXPx8nKZDqN3XpnGKCfw",
            "use": "sig"
        }
    ]
} 

I am thinking if the issue now is my local Apache server forcing redirect to https due to some settings I did previously If all else fails, I will be trying this on a new computer with the default apache settings to test

Cvmcosta commented 3 years ago

Can you show me both you current Platform and Tool registrations just so we can make sure everything is correct? But yes, it might be an issue with apache redirection.

vandergav commented 3 years ago

Sure thank you so much for looking into this. image image

Cvmcosta commented 3 years ago

Okay, everything seems to be in place. I believe any issue now is probably being caused by Apache redirection and self signed ssl certificates

vandergav commented 3 years ago

To update, I changed a line in config.php (in the root of Moodle's source code folder) from $CFG->wwwroot = 'https://localhost/moodle'; to $CFG->wwwroot = 'http://localhost/moodle'; and this solved the redirection issue but now I am getting back the error {"status":401,"error":"Unauthorized","details":{"description":"Error validating ltik or IdToken","message":"invalid algorithm"}}

I decoded the query string parameters and got this: image It shows that the encryption algorithm is using HS256

Cvmcosta commented 3 years ago

Hello @vandergav. Can you show me logs up until this error? I want to pinpoint exactly where this error is being thrown.

vandergav commented 3 years ago

Sure, using the index.js code above and adding console.log(await platform.platformJSON()) and switch (authConfig.method) { ..., here are the logs (sorry for the spam): image image image image image image image image image image

Cvmcosta commented 3 years ago

So no logs about the actual error show up? I need to know at what point of the flow the error happens. Is it during the Launch or after the Launch while validating the ltik?

Cvmcosta commented 3 years ago

The first argument of the lti.setup method is the encryption key, did you use a Public Key as your encryption key? Only two places in the code could have thrown this issue. One of them would suggest an issue in your Moodle instance, the other one would suggest an issue with the encryption key.

vandergav commented 3 years ago

Hi Carlos, thank you for pointing me to the lti.setup method, I believe the issue now is with the encryption key. Can I ask in what format I should pass the key? With this, I am getting the below error image UnhandledPromiseRejectionWarning: Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

[Update] I changed my .env and index.js and managed to get logs for the error: My .env image then in index.js image Error I got before clicking the external tool in Moodle image Error I got after clicking the external tool in Moodle (sorry for the spam) image image image image image

Cvmcosta commented 3 years ago

Hello again, sorry for the late reply. The error you are seeing is caused by the change in the encryption key, since the key pairs generated for each Platform are encrypted before they are stored. You should revert to your old key, it doesn't have to be a RSA Key, i just wanted to know that so i could eliminate some possibilities.

Can you please revert back to the old key and show me the logs generated up until the error shows up so i can know exactly where the flow is failing.

vandergav commented 3 years ago

Hi Carlos no it's okay thank you for helping! I lost the old key and by old key do you mean the public key you get from here: https://lti-ri.imsglobal.org/keygen/index? I am specifying Public keyset in Moodle to be http://localhost:8100/keys and using lti.setup(LTI_KEY, ... where LTI_KEY is the public key I got from https://lti-ri.imsglobal.org/keygen/index for a specific tool. I just created a new tool with a new clientid but used the same LTI_KEY, and I am getting back the same JsonWebTokenError: invalid algorithm error. I had deleted the platform prior to adding the new tool.

image image image image image

Cvmcosta commented 3 years ago

No, the encryption key can be any string (usually a big string is better). Were you using an RSA Key as the encryption key (first parameter of lti.setup)? You don't need to get any key from https://lti-ri.imsglobal.org/keygen/index, these are only used for testing the tools provided by IMS.

You can try changing the LTI_KEY to any big random string and them trying to register and use a new Platform just like you did now.

My theory is that using an RSA Key as the LTI_KEY causes the verification code to look for an algorithm that i am not passing in the core (I was not expecting people to use RSA Keys haha).

vandergav commented 3 years ago

Hi Carlos, I can't thank you enough. It's finally working after I created a new tool and used "LTI_KEY" for lti.setup(LTI_KEY, ...

Untitled

Yes, what you said about "using an RSA Key as the LTI_KEY causes the verification code to look for an algorithm that i am not passing in the core" is true then Thanks again Carlos your excellent support

Cvmcosta commented 3 years ago

Great! I am really happy it worked! I might add a note to the docs letting people know to use a big random string as the encryption key.

I'll be closing this issue for now. Please let me know if you need any more help.