I tried to create LTI Consumer using Node.JS and html , JS .
when I click the button I get the request params from the nodejs with the signature , and call to moodle , and I got 302 found with the location of the return URL with eror :
https://DOMAIN/lti?lti_errormsg=Sorry%2C+there+was+an+error+connecting+you+to+the+application.<i_errorlog=Debug+error%3A+OAuth+signature+check+failed+-+perhaps+an+incorrect+secret+or+timestamp.
what wrong with my signature ?
this is my nodejs index.js :
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const btoa = require('btoa');
const md5 = require('md5');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const PORT = process.env.PORT || 3030;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
app.get('/' , (req, res)=>{
res.sendFile(path.join(__dirname, 'public', 'index.html'));
})
app.get('/lti' , (req, res)=>{
res.sendFile(path.join(__dirname , 'public' , 'lti.html'));
})
function generateNonce() {
const mt = String(new Date().getTime() / 1000);
const rand = String(Math.floor(Math.random() * 1000000));
return md5(mt + rand); // You would need an md5 function here, possibly from a library or implementation
}
app.get('/sign' , (req , res)=>{
var oauth = require('oauth-sign');
var action = 'https://DOMAIN/enrol/lti/tool.php?id=2';
var method = 'POST';
var timestamp = Math.floor(Date.now() / 1000);
console.log(timestamp);
var basicParams = {
// LTI Required Parameters
// OAuth 1.0a Required Parameters
oauth_nonce: generateNonce(),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: timestamp,
oauth_version: '1.0',
oauth_consumer_key: 'BBBB',
resource_link_id: '1',
lti_message_type: 'basic-lti-launch-request',
lti_version: 'LTI-1p0',
launch_presentation_return_url: 'https://DOMAIN/lti',
roles: 'Instructor,urn:lti:sysrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Administrator',
context_id: '2',
context_label: 'aaa',
context_title: 'aaa from moodle',
resource_link_title: 'aaa',
resource_link_description: 'a',
context_type: 'CourseSection',
launch_presentation_locale: 'en',
tool_consumer_info_product_family_code: 'moodle',
tool_consumer_info_version: '2023100900',
oauth_callback: 'about:blank',
tool_consumer_instance_name: 'New Site',
tool_consumer_instance_description: 'New Site',
lis_person_sourcedid: '',
user_id: '2',
tool_consumer_instance_guid: 'dd1bd87b4dc23b9665f4696523508c92',
launch_presentation_document_target: 'window',
lis_course_section_sourcedid: '',
ext_lms: 'moodle-2'
};
let secret = 'SECRET';
var signature = oauth.hmacsign(method, action, basicParams, secret , '' );
basicParams.oauth_signature = signature;
console.log(basicParams);
res.json(basicParams);
})
and this is the index.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LTI Request Example</title>
</head>
<body>
<pre><code class="language-json"></code></pre>
<form id="ltiForm"></form>
<button type="submit" id="submitBtn" form="ltiForm">POST to LTI Tool Provider!</button>
<iframe id="resultFrame" width="600" height="400" frameborder="0"></iframe>
<script>
async function makeLTILaunchRequest() {
let sign = await fetch('/sign');
let params = await sign.json();
var action = 'https://DOMAIN/enrol/lti/tool.php?id=2';
var method = 'POST';
var form = document.querySelector("#ltiForm");
form.action = action;
form.method = method;
for (var name in params) {
var node = document.createElement("input");
node.name = name;
node.type = 'hidden';
node.value = params[name];
form.appendChild(node);
}
var output = document.querySelector("code");
output.textContent = JSON.stringify(params, null, 2);
console.log(form);
var meta = document.querySelector("body > script");
console.log(meta);
}
makeLTILaunchRequest();
</script>
</body>
</html>