I'm using the node-linkedin npm package to authenticate and read information about from other users (name, job title, company name, profile pic, shared connections). I can correctly receive and store the access token (verified in my own LinkedIn profile's approved apps & console logging the token), but I am unable to return any of the requested information. My calls are copied & pasted from the package docs, but it returns the following:
2018-02-28T03:46:53.459839+00:00 app[web.1]: { errorCode: 0,
2018-02-28T03:46:53.459843+00:00 app[web.1]: message: 'Unknown authentication scheme',
2018-02-28T03:46:53.459845+00:00 app[web.1]: requestId: '3B55EVY7XQ',
2018-02-28T03:46:53.459847+00:00 app[web.1]: status: 401,
2018-02-28T03:46:53.459848+00:00 app[web.1]: timestamp: 1519789613443 }
I have included my routes below. Solely for the purpose of testing, myToken and linkedin are server-side global variables to the linkedin-controller scope. (I understand this will need to change for the final product, which is a student project.)
app.get('/companies', function (req, res) {
console.log(linkedin.connections.config.accessToken);
linkedin.companies_search.name('facebook', 1, function(err, company) {
console.log('Merpy merpy mc merpers'
,company);
// name = company.companies.values[0].name;
// desc = company.companies.values[0].description;
// industry = company.companies.values[0].industries.values[0].name;
// city = company.companies.values[0].locations.values[0].address.city;
// websiteUrl = company.companies.values[0].websiteUrl;
res.redirect("/");
});
});
app.get('/companies2', function (req, res) {
linkedin.companies.company('162479', function(err, company) {
console.log(company);
res.redirect("/");
});
});
app.get('/connections', function (req, res) {
linkedin.connections.retrieve(function(err, connections) {
console.log(connections);
res.redirect("/");
});
});
This is my authorization code, which appears to work:
app.get('/auth', function (req, res) {
// This is the redirect URI which linkedin will call to and provide state and code to verify
/**
*
* Attached to the redirect_uri will be two important URL arguments that you need to read from the request:
code — The OAuth 2.0 authorization code.
state — A value used to test for possible CSRF attacks.
*/
//TODO: validate state here to secure against CSRF
var error = req.query.error;
var error_description = req.query.error_description;
var state = req.query.state;
var code = req.query.code;
if (error) {
next(new Error(error));
}
/**
*
* The code is a value that you will exchange with LinkedIn for an actual OAuth 2.0 access
* token in the next step of the authentcation process. For security reasons, the authorization code
* has a very short lifespan and must be used within moments of receiving it - before it expires and
* you need to repeat all of the previous steps to request another.
*/
//once the code is received handshake back with linkedin to send over the secret key
handshake(req.query.code, res);
});
function handshake(code, ores) {
//set all required post parameters
var data = querystring.stringify({
grant_type: "authorization_code",
code: code,
redirect_uri: OauthParams.redirect_uri,//should match as in Linkedin application setup
client_id: OauthParams.client_id,
client_secret: OauthParams.client_secret// the secret
});
var options = {
host: 'www.linkedin.com',
path: '/oauth/v2/accessToken',
protocol: 'https:',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data)
}
};
var req = http.request(options, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
//once the access token is received store it
myToken = JSON.parse(data);
linkedin = Linkedin.init(myToken);
ores.redirect("/");
});
req.on('error', function (e) {
console.log("problem with request: " + e.message);
});
});
req.write(data);
req.end();
}
In my troubleshooting research, it seems I need to pass the token into the request; however, I can't find anywhere or any way to do so in the package. And with as many daily downloads as the package has, I can't possibly be the only one to experience this error. The author's Issues section of GitHub were unhelpful, as were other searches for this package's error.
My deployment: https://linkedin-api-test.herokuapp.com/
(When visiting the deployment, you must click the blue "Want to
connect to LinkedIn?" link prior to manually changing the uri
according to the routes. The results will also only display in the
Heroku logs, which is most likely largely unhelpful to you. It was
supposed to be a simple test, so I simply stole the front end from my
prior project.)
My Repo: https://github.com/SteveSonoa/LinkedIn-Test
node-linkedin Docs: https://github.com/ArkeologeN/node-linkedin/blob/master/README.md
This is my first question I haven't been able to find the answer to; I apologize if I left out anything important while asking. Thank you in advance for any help!
The solution was to pass the following token code into the linkedin variable instead of simply passing myToken:
linkedin = Linkedin.init(myToken.access_token || myToken.accessToken);
I don't understand the downvote, as no comments were left; I apologize if I left out important or generally expected information, as this was the first question I've asked. I want to make sure the solution is posted for anyone coming after me with the same issue. This issue is now solved.
Related
I'm new to IBM cloud and I'm trying to build an application where I can write a text, press a button and that text is analysed by the service tone analyser returning a JSON so I can show it.
I have created an instance of said service and I have connected it to my application (a toolchain) using the 'connections' tab on the service.
I also have this code on the app.js file on my app:
const ToneAnalyzerV3 = require('ibm-watson/tone-analyzer/v3');
const { IamAuthenticator } = require('ibm-watson/auth');
const toneAnalyzer = new ToneAnalyzerV3({
version: '2019-10-10',
authenticator: new IamAuthenticator({
apikey: [API key found in service credentials],
}),
url: [API url found in service credentials],
});
app.get('/', function(req, res) {
res.render('index');
});
app.post('/api/tone', async function(req, res, next) {
try {
const { result } = await toneAnalyzer.tone(req.body);
res.json(result);
} catch (error) {
next(error);
}
});
The problem is that when I make the following call on my javascript:
$.post( "/api/tone", {text: textInput}, function(data){
console.log(data);
});
I get the error: 500 (Internal Server Error).
Does anybody know what I am doing wrong?
The issue is that you are sending req.body to be analysed for tone. If you take a look at the API Docs - https://cloud.ibm.com/apidocs/tone-analyzer?code=node#tone - you will see that you only need to send
const toneParams = {
toneInput: { 'text': text },
contentType: 'application/json',
};
I doubt very much that req.body has a toneInput field, and if it does have contentType it may not be set to one of the allowable values.
I am using the npm package activedirectory to connect to my company's domain controller for the purpose of authenticating users on an internal website. The plan is for users to enter their windows domain credentials into the website's login page (written with React). The website will forward the credentials to a backend Express server that looks like this:
const express = require('express');
const bodyParser = require('body-parser');
const ActiveDirectory = require('activedirectory');
const app = express();
app.use(bodyParser.urlencoded({
extended: false,
}));
app.get('/ldap', (request, response) => {
// set http header for json
response.setHeader('ContentType', 'application/json');
// get the username and password from the query
const username = request.query.username + '#internal.mycompany.com';
const password = request.query.password;
console.log(username, password);
// create a new active directory connection
let activeDirectory = new ActiveDirectory({
url: 'LDAP://internal.mycompany.com',
baseDN: 'DC=mycompany,DC=com',
});
// attempt authentication
activeDirectory.authenticate(username, password, (error, authenticated) => {
if(error) {
console.log('ERROR: ' + JSON.stringify(error));
}
else {
console.log('Authentication successful');
}
// respond
response.send(JSON.stringify({
error: error,
authenticated: authenticated,
}));
});
});
app.listen(3001, () => {
console.log('Express server running on port 3001.');
});
The React frontend executes this when the 'Log In' button is clicked:
login() {
console.log('authenticating with username: ', this.state.username, ' and password: ', this.state.password);
fetch(`/ldap/?username=${encodeURIComponent(this.state.username)}&password=${encodeURIComponent(this.state.password)}`).then((response) => {
return response.json();
}).then((response) => {
console.log(response);
this.props.setLoggedIn(response.authenticated);
this.setState({
loginFailed: !response.authenticated,
redirectToHome: response.authenticated,
});
});
}
It calls fetch to ask the Express server to authenticate the credentials, and sets some global loggedIn state accordingly. It also sets local state to display an error message if the login attempt failed or redirect the user on to the main website homepage if the login attempt was successful.
Leaving the username and password blank yields this response:
{
error: {
code: 49,
description: "The supplied credential is invalid",
errno: "LDAP_INVALID_CREDENTIALS",
},
authenticated: false,
}
Typing in a valid username and password yields this response:
{
error: null,
authenticated: true,
}
This is all as expected up to this point.
However, typing in random characters for the username and password yields one of 2 responses. It either authenticates successfully, which shouldn't happen, or it gives this:
{
error: {
lde_dn: null,
lde_message: "80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1",
},
authenticated: false,
}
QUESTION:
Why would giving the domain controller garbage credentials cause it to return authenticated: true? And why only sometimes? Some information must be cached or remembered somewhere. I tried restarting the Express server and I tried waiting a day for something to expire.
Note: I was planning to encrypt/scramble the passwords so that they are not being sent in plain text, but if there is an even better way to send them securely, please leave a comment about how to improve this. But for now the main question is about the incorrect Active Directory/LDAP authentication.
This information: https://www.rfc-editor.org/rfc/rfc4513#section-6.3.1 essentially tells us that getting authenticated: true may not actually mean anything. The solution I came up with is to try and query the Active Directory to see if the user that just authenticated exists and/or try doing an/some operation(s) as the newly-authenticated user. If the operation(s) fail(s), then the login was not valid.
Additionally, there was an error in the above code. baseDN should have been 'DC=internal,DC=mycompany,DC=com'.
I have an authentication service built on IdentityServer3 and a WebAPI Shopper service that is secured with IdentityServer Bearer Token Authentication. I've built a simple MVC client app that can access the Shopper service either as a client app with an access token or on behalf of an authenticated user with an identity token. The Shopper service will return more data to the authenticated user.
Now I'm trying to build a JavaScript client that does the same two-tier level of access to the Shopper service. So far, following some IdentityServer3 JavaScript client examples, I've got the JS client successfully calling the Shopper service on behalf of an authenticated user. (I will probably need to reorganize the code a bit to accommodate the non-authenticated scenario, but that shouldn't be too difficult.) What I don't know how to do from the JavaScript code is request the client access token from the Authentication service, i.e. the JavaScript equivalent of the server-side TokenClient.RequestClientCredentialsAsync("shopper-service") in the MVC client. Does anyone know how to request that token from JavaScript or know of a sample that shows how to do it? Here's the JavaScript code that I have so far for the authenticated case and below that is the working MVC client code:
function display(selector, data) {
if (data && typeof data === 'string') {
data = JSON.parse(data);
}
if (data) {
data = JSON.stringify(data, null, 2);
}
$(selector).text(data);
}
var settings = {
authority: 'https://localhost:44332',
client_id: 'js-sample',
popup_redirect_uri: 'http://localhost:15264/popup.html',
response_type: 'id_token token',
scope: 'openid orvis-shopper-service',
filterProtocolClaims: false
};
var manager = new Oidc.UserManager(settings);
var user;
manager.events.addUserLoaded(function (loadedUser) {
user = loadedUser;
display('.js-user', user);
});
$('.js-login').on('click', function () {
manager
.signinPopup()
.catch(function (error) {
console.error('error while logging in through the popup', error);
});
});
$('.js-call-api').on('click', function () {
var headers = {};
if (user && user.access_token) {
headers['Authorization'] = 'Bearer ' + user.access_token;
}
$.ajax({
url: 'https://localhost:44368/api/Shopper/{5FA13934-AD20-4AB2-A386-11653D71AE55}',
method: 'GET',
dataType: 'json',
headers: headers
}).then(function (data) {
display('.js-api-result', data);
}).catch(function (error) {
display('.js-api-result', {
status: error.status,
statusText: error.statusText,
response: error.responseJSON
});
});
});
The client app code works as I intend and looks like this:
public async Task<ActionResult> Index()
{
string tokenValue;
var user = User as ClaimsPrincipal;
if (user.Identity.IsAuthenticated)
{
tokenValue = user.FindFirst("access_token").Value;
}
else
{
var tokenResponse = await GetTokenAsync();
tokenValue = tokenResponse.AccessToken;
}
var result = await CallShopperService(tokenValue);
ViewBag.Json = result;
return View();
}
private async Task<TokenResponse> GetTokenAsync()
{
var client = new TokenClient(
"https://localhost:44332/connect/token",
"mvc-sample-svc",
"mvcsamplesecret");
return await client.RequestClientCredentialsAsync("shopper-service");
}
private async Task<string> CallShopperService(string token)
{
var client = new HttpClient();
client.SetBearerToken(token);
var json = await client.GetStringAsync("https://localhost:44368/api/Shopper/{5FA13934-AD20-4AB2-A386-11653D71AE55}");
return json;
}
JS implicit client is also able to get back you access token, as long as you set token in response_type which you did according to your pasted script.
The Oidc-client.js builds up an authorize challenge URL and redirect browser to it, which is where login flow begins. After user signs in, then they get bounced back to your client page following same redirect path. When your client page loaded (depends on which flow your client is configured, it should be hash fragment by default), oidc-client grabs token from URL (everything after #) and transforms it into JSON object then save in local storage (which means you can check it there too).
I would suggest following method to help you debug:
Turn on traffic monitoring tool (e.g. fiddler) to ensure response that returned back from your identity server after login does contains access token (you can decode token string using https://jwt.io/), if not, check if authorize request url formed correctly
If response returned from identity server does contains access token , then you can debug oidc-client.js javascript by setting a breakpoint at _signinEnd method
Hope it helps
Updated, from comment section
For "anonymous token" senario? See this example if that is what you are looking for github.com/IdentityServer/IdentityServer3/issues/1953
I am using satellizer for authentication in a MEAN app. After auth is complete i want to get the profile picture of the user. The following is my code.
Angular Controller
(function(){
'use strict';
angular
.module('nayakans07')
.controller('RegisterController', registerController);
function registerController ($scope, $auth) {
$scope.authenticate = function (provider) {
$auth.authenticate(provider)
.then(function (res) {
console.log(res);
}, authErrorHandler);
};
function authErrorHandler (err) {
console.log(err);
}
}
})();
Node.js Route Handler
(function () {
'use strict';
var request = require('request');
var qs = require('querystring');
var tokenHelper = require('../helpers/tokenHelper.js');
var config = require('../config/configuration.js');
var Member = require('../models/memberModel.js');
module.exports = function (req, res) {
var accessTokenUrl = 'https://graph.facebook.com/oauth/access_token';
var grapApiProfileUrl = 'https://graph.facebook.com/me';
var graphApiProfileImageUrl = 'https://graph.facebook.com/me/picture?width=250&json=true';
var params = {
client_id: req.body.clientId,
redirect_uri: req.body.redirectUri,
code: req.body.code,
client_secret: config.FACEBOOK_SECRET
};
request.get({
url: accessTokenUrl,
qs: params
}, function (err, response, accessToken) {
accessToken = qs.parse(accessToken);
request.get({
url: grapApiProfileUrl,
qs: accessToken,
json: true
}, function (err, response, profile) {
console.log(profile);
Member.findOne({
facebookId: profile.id
}, function (err, existingMember) {
if (existingMember) {
return res.status(200).send({
user: existingMember,
token: tokenHelper.createToken(existingMember, req, res)
});
} else {
request.get({
url: graphApiProfileImageUrl,
qs: accessToken,
}, function (err, response, profileImage) {
console.log(profileImage);
var fbData = {
facebookId: profile.id,
fullName: profile.name,
email: profile.email,
profileImage: profileImage
};
return res.status(200).send(fbData);
});
}
});
});
});
};
})();
After i get the access token from facebook i call the https://graph.facebook.com/me/picture?width=250&json=true endpoint with the access token to get the profile pic link. In the Facebook's API Explorer calling the same endpoint with the token i get the profile image url but when i call it from node.js endpoint handler i get some binary like data. part of the response is here.
����JFIF���Photoshop 3.08BIM�gtc2XjTON-90Jqu9JFj83(bFBMD01000ac0030000b909000069110000fe120000dc14000069190000c02400002326000029280000872a0000fd3e0000��ICC_PROF
How can i get the URL for the profile image instead of this response.
Not sure if this is still the case on the version of the API that you're using, but it used to cause a 301 redirect to the image if not passed the correct parameter (despite what their API Explorer suggests...) You can check your browser's network monitoring to verify this.
There are various sources for this, including the Official Facebook Graph-API documentation, which states:
By default this edge will return a 302 redirect to the picture image. To get access to the data about the picture, please include redirect=false in your query.
(Source: https://developers.facebook.com/docs/graph-api/reference/user/picture/#Reading )
Along with some (older, but similar) SO answers: https://stackoverflow.com/a/7881978/624590
You have a couple options. One is to add the redirect=false query param (per the Facebook documentation, above).
Another option is explicitly asking for the URL by adding an additional query param, &fields=url (the answer I linked to above suggests &fields=picture, but as far as I can tell, that's out of date). If you need the height and width in the response, just add those to the query: &fields=url,height,width. In my experience with the Facebook API, explicitly asking for fields tends to be a more reliable way to go.
I'm creating an HTML 5 client to app services, however our app services are enterprise so behind an apigee gateway proxy ( not directly through api.usergrid.com).
I'm initializing like this:
$(function() {
var client = new Apigee.Client({
orgName:'myorg',
appName:'sandbox',
monitoringEnabled:false,
URI:'https://prod.OURURL.com/appservices/v1'
});
var username = "myusername";
var password = "mypass";
client.login(username, password,
function (err) {
if (err) {
console.log('There was an error logging you in.');
} else {
//login succeeded
client.getLoggedInUser(function(err, data, user) {
if(err) {
//error - could not get logged in user
console.log("error on lvl2");
} else {
if (client.isLoggedIn()){
appUser = user;
console.log('data')
// showFullFeed();
}
}
});
}
}
);
});
I'm immediately getting:
Error: Apigee APM configuration unavailable.
and then of course:
There was an error logging you in.
using the trace tool in the proxy I can see this errorr on the request to /proxy_path/org/app/apm/apigeeMobileConfig
{"timestamp":"1398263318219","duration":"0","error":"illegal_argument","exception":"java.lang.IllegalArgumentException","error_description":"JSON source MUST not be null"}
of course this is all called by the above code.
thank you in advance.
[EDIT FOR MORE INFORMATION]
Just tested with my own private org, so not setting the options.URI param, the second log message is normal as I had not created the app user, however the initialization is NOT working on the enterprise org, so this:
var client = new Apigee.Client({
orgName:'myorg',
appName:'sandbox',
monitoringEnabled:false,
URI:'https://prod.OURURL.com/appservices/v1'
});
is returning the APM error.
It seems you need to enable some of the options in the app services app ( inthe configuration option) for this api call to return something, thus enabling the sdk.