I'm attempting to integrate LinkedIn Learning Single-Sign-On via an LTI connection, however I'm always faced with the response: LTI_FAILED_AUTHENTICATION.
LinkedIn Learning - LTI_FAILED_AUTHENTICATION
When I test it out on the Saltire test platform, it strangely works.
The parameters match what I am sending from the code below:
Saltire LTI Success authentication
Have tried copying over the the values of oauth_nonce, timestamp and oauth_signature from Saltire to my page, and that worked also, which scores out the possibility of domain whitelisting requirement.
LinkedIn support have come back saying there seems to be something wrong with the generated signature, but I'm not sure what is wrong about it, since that is generated by the parameters passed.
Is there something incorrectly setup from my page which I am not seeing?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="robots" content="noindex" />
<title>Access LinkedIn Learning</title>
<script src="bundle.js"></script>
</head>
<body>
<form id="id_frmConnect" name="frmConnect" enctype="application/x-www-form-urlencoded">
</form>
<script>
var oauth = require('oauth-sign');
var action = 'https://www.linkedin.com/checkpoint/enterprise/login/[accountID]?application=learning&redirect=https://www.linkedin.com/learning/me';
var method = 'POST';
var consumer_key = '************';
var consumer_secret = '************';
var timestamp = Math.round(Date.now() / 1000);
var params = {
lti_message_type: 'basic-lti-launch-request',
lti_version: 'LTI-1p0',
oauth_callback: 'about:blank',
oauth_consumer_key: consumer_key,
oauth_nonce: btoa(timestamp),
oauth_signature_method: 'HMAC-SHA1',
oauth_timestamp: timestamp,
oauth_version: '1.0',
user_id: 'S495696'
};
var signature = oauth.hmacsign(method, action, params, consumer_secret);
params.oauth_signature = signature;
var form = document.querySelector("#id_frmConnect");
form.action = action;
form.method = method;
for (var name in params) {
var node = document.createElement("input");
node.type = 'hidden';
node.name = name;
node.value = params[name];
form.appendChild(node);
}
</script>
</body>
</html>
I figured out the issue. By using the Saltire test tool, I was able to verify that my signature was generated correctly when using their testing URL: https://lti.tools/saltire/tp
You can play with an example here: https://learningcom.github.io/ltitest/index.html
So after looking at the LinkedIn URL, I discovered that the signature was getting generated with an unnecessary long URL which contained parameters.
Removed: ?application=learning&redirect=https://www.linkedin.com/learning/me
Therefore, I shortened the URL to:
var action = 'https://www.linkedin.com/checkpoint/enterprise/login/[accountID]';
No more errors!
Related
So, I am fairly new to C Sharp and I can't figure out how I can basically submit a POST request via javascript (in the front-end) using fetch, send it to a C Sharp back-end to process the data, and then receive it back to my site.
My final goal is to achieve a search engine, where I can get user input from javascript and send it to my C Sharp back-end so I can crawl sites from a database and return the ones which contain the highest volume of the key-word.
Here is what I am currently working within the front-end:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./index.css">
<title>Search Engine</title>
</head>
<body>
<form class="search">
<h2>Search Engine</h2>
<input name="search" type='text' placeholder='Search' />
<button type="submit">Submit</button>
</form>
<script>
const endpoint = "file to send data to";
const formEl = document.querySelector('form');
formEl.addEventListener('submit', async(e) => {
e.preventDefault();
const formData = new FormData(formEl);
const formDataSerialized = Object.fromEntries(formData);
try {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(formDataSerialized),
headers: {
'Content-Type': 'application/json'
}
});
const json = await response.json();
console.log(json);
} catch(e) {
console.error(e);
alert('there was an error!');
}
})
</script>
</body>
</html>
As you can see, I have a basic form that submits a user search, however, the endpoint in my javascript function is currently empty, since I am not sending the data anywhere.
I was looking around for a long while for a solution where I can make a web server with C Sharp and accept the data that way, however didn't find anything which I can use.
Can anyone please help? I would appreciate it a lot.
I'm having trouble reliably triggering a Firebase Cloud Function via the HTTP trigger method. It doesn't work in all browsers, and sometimes only works if you reload the page. The html webpage is hosted with Firebase Hosting.
Premise is simple, main html page has a button which when pressed, sends the HTTP request to the Cloud Function, and then navigates to another page saying done. The Cloud Function sends an FCM notification and works reliably when manually using the HTTP request url.
Is there something wrong with my HTTP request in scripts.js? What would make this very inconsistent behaviour? It works generally in Chrome, not in Firefox, and sometimes in Edge.
index.html looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel=icon href=favicon.ico>
<title>Title</title>
</head>
<body>
<h1>New heading</h1>
<input type="button" value="Send FCM" onclick="doThing()">
<script src="scripts.js"></script>
</body>
</html>
scripts.js looks like this
var request = require('request')
function doThing(){
const url = '<HTTP request URL>';
request(url, function(error, response, body){
if (!error && response.statusCode == 200){
window.location.href = "outro.html";
}
})
}
and outro.html looks like this;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Title</title>
<body>
<h1>Cya</h1>
</body>
</body>
</html>
For clarity, this is the Cloud Function script:
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.https.onRequest((request, response) =>{
var topic = 'all';
var payload = {
notification:{
title: 'Title!',
body: 'Test!'
},
};
return admin.messaging().sendToTopic(topic, payload).then((res) =>{
response.status(200).send("ok");
return console.log("Success:");
}).catch((err) =>{
console.log("Error: ", err);
response.status(500).send("bad");
});
});
All are deployed with firebase deploy.
I'm learning about sending forms through JavaScript by manually building XMLHttpRequest.
In the end of given example, there's a note:
Note: This use of XMLHttpRequest is subject to the same-origin policy if you want to send data to a third party web site. For cross-origin requests, you'll need CORS and HTTP access control.
I'd like to test the example locally, though. What can I change to make it work locally? The address of posting request?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>Click me!</button>
<script>
function sendData(data) {
console.log('Sending data');
const XHR = new XMLHttpRequest();
let urlEncodedData = "",
urlEncodedDataPairs = [],
name;
for (name in data) {
urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
}
urlEncodedData = urlEncodedDataPairs.join('&').replace('/%20/g', '+');
XHR.addEventListener('load', function (event) {
alert('Yeah! Data sent and response loaded.');
});
XHR.addEventListener('error', function (event) {
alert('Oops! Something went wrong.');
});
XHR.open('POST', 'https://example.com/cors.php');
XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
XHR.send(urlEncodedData);
}
const btn = document.querySelector('button');
btn.addEventListener('click', function () {
sendData({ test: 'ok' });
})
</script>
</body>
</html>
You can use a fake REST API service, like https://reqres.in. Basic usage is free and requires no registration whatsoever.
They usually send back fake data and don't require CORS.
You just need to change the URL to https://reqres.in/api/users for example.
I am using the following two files attached.
index2.html file located on the local server which is calling the JavaScript file which is also located on the local machine
fetch-ajax3.js - JavaScript file located on the local server consisting of the function and method to authenticate and authorize the API call and retreive data and post it in the console.
I am not sure what to input in redirect URI.
Can someone help?
Resolution - i was able to resolve the issue after whitelisting the callback uri in the spotify api app.
const hash = window.location.hash
.substring(1)
.split('&')
.reduce(function (initial, item) {
if (item) {
var parts = item.split('=');
initial[parts[0]] = decodeURIComponent(parts[1]);
}
return initial;
}, {});
window.location.hash = '';
// Set token
let _token = hash.access_token;
const authEndpoint = 'https://accounts.spotify.com/authorize';
// Replace with your app's client ID, redirect URI and desired scopes
const clientId = '';
const redirectUri = '';
const scopes = [
'user-top-read'
];
// If there is no token, redirect to Spotify authorization
if (!_token) {
window.location = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join('%20')}&response_type=token&show_dialog=true`;
}
// Make a call using the token
$.ajax({
url: "https://api.spotify.com/v1/me/top/artists",
type: "GET",
beforeSend: function(xhr){xhr.setRequestHeader('Authorization', 'Bearer ' + _token );},
success: function(data) {
// Do something with the returned data
data.items.map(function(artist) {
let item = $('<li>' + artist.name + '</li>');
item.appendTo($('#top-artists'));
});
}
});
<html>
<head>
<title>Spotify Implicit Grant Template</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://sp-bootstrap.global.ssl.fastly.net/8.0.0/sp-bootstrap.min.css" rel="stylesheet" />
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script src="./fetch-ajax3.js" defer></script>
</head>
<body class="container">
<h1 class="text-salmon">Spotify Implicit Grant Template</h1>
<h3>This app uses the implicit grant authorization flow to authenticate users and get user data.</h3>
<p>
Here are your top artists on Spotify:
<ol id="top-artists"></ol>
</body>
</html>
After whitelisting the callback url in the app , i was able to connect it.
specify your URL as Http for localhost in the app settings in your Spotify dashboard
You need to add your URL in Redirect URI which will whitelist your URL. It works for me.
I am trying to make the codes you see here:
https://glitch.com/edit/#!/amusing-swallow?path=index.html:7:68
executable on my node js application. Please note that the codes on that link is working.
What I did is that I set up an environment like this:
root folder
--public
--index.html
--script.js
--app.js
--package.json
And then I copied the html code to my index.html file like this:
<!DOCTYPE html>
<html>
<head>
<title>Spotify Implicit Grant Template</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://sp-bootstrap.global.ssl.fastly.net/8.0.0/sp-bootstrap.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
</head>
<body class="container">
<h1 class="text-salmon">Spotify Implicit Grant Template</h1>
<h3>This app uses the implicit grant authorization flow to authenticate users and get user data.</h3>
<p>
Here are your top artists on Spotify:
<ol id="top-artists"></ol>
</p>
<script src="../script.js" type='text/javascript'></script>
</body>
</html>
Nothing special and then I made the script.js a self invoking function like this:
(function() {
// Get the hash of the url
const hash = window.location.hash
.substring(1)
.split("&")
.reduce(function(initial, item) {
if (item) {
var parts = item.split("=");
initial[parts[0]] = decodeURIComponent(parts[1]);
}
return initial;
}, {});
window.location.hash = "";
// Set token
let _token = hash.access_token;
const authEndpoint = "https://accounts.spotify.com/authorize";
// Replace with your app's client ID, redirect URI and desired scopes
const clientId = "xxxxxxxxxxxxxxxxxxxxxxxx";
const redirectUri = "http://localhost:8888/callback/";
const scopes = ["user-top-read"];
// If there is no token, redirect to Spotify authorization
if (!_token) {
window.location = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join(
"%20"
)}&response_type=token&show_dialog=true`;
}
// Make a call using the token
$.ajax({
url:
"https://api.spotify.com/v1/search?query=tania+bowra&offset=0&limit=20&type=artist",
type: "GET",
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + _token);
},
success: function(data) {
// Do something with the returned data
data.items.map(function(artist) {
let item = $("<li>" + artist.name + "</li>");
item.appendTo($("#top-artists"));
});
}
});
})();
And then finally on my app.js for express:
var express = require("express"); // Express web server framework
var app = express();
app.use(express.static(__dirname + "/public"));
console.log("Listening on host 8888......");
app.listen(8888);
So I tried to run this and whenever I visit the localhost:8888, I keep on getting these:
GET http://localhost:8888/script.js 404 (Not Found)
Refused to execute script from 'http://localhost:8888/script.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
I am not sure why. But I just copied the same exact code and just put my credentials there but it won't still work. Any idea what am I doing wrong?