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.
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'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.
I have a problem with implementing an Oauth2 authentication in node.js application where I need to add one extra parameter in the authorization request, but the module simply ignores the "unknown" parameters.
My code is attached below. The parameter being ignored is APIName.
var OAuth2Strategy = require('passport-oauth2').Strategy;
// load the auth variables
var configAuth = require('./auth');
module.exports = function(passport) {
passport.use('ihealth', new OAuth2Strategy({
authorizationURL: 'https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/',
tokenURL: 'https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/',
clientID: configAuth.iHealthAuth.clientID,
clientSecret: configAuth.iHealthAuth.clientSecret,
callbackURL: configAuth.iHealthAuth.callbackURL,
APIName : 'OpenApiActivity'
},
function(token, refreshToken, profile, done) {
// ...
}
));
};
The reason that I know APIName is being ignored, is that I see the URL in the browser:
https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/?response_type=code&redirect_uri=SOMEREDIRECTURI&client_id=SOMECLIENTID
I am wondering how to enable adding extra parameters to the authorization request? Maybe by overriding the function OAuth2Strategy.prototype.authorizationParams in node_modules/passport_oauth2/lib/strategy.js, which looks like this in the donwloaded file:
/**
* Return extra parameters to be included in the authorization request.
*
* Some OAuth 2.0 providers allow additional, non-standard parameters to be
* included when requesting authorization. Since these parameters are not
* standardized by the OAuth 2.0 specification, OAuth 2.0-based authentication
* strategies can overrride this function in order to populate these parameters
* as required by the provider.
*
* #param {Object} options
* #return {Object}
* #api protected
*/
OAuth2Strategy.prototype.authorizationParams = function(options) {
return {};
};
You can override OAuth2Strategy.prototype.authorizationParams as follows
var myStrategy = new OAuth2Strategy({
authorizationURL: 'https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/',
tokenURL: 'https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/',
clientID: configAuth.iHealthAuth.clientID,
clientSecret: configAuth.iHealthAuth.clientSecret,
callbackURL: configAuth.iHealthAuth.callbackURL
},
function(token, refreshToken, profile, done) {
// ...
});
myStrategy.authorizationParams = function(options) {
return {
APIName : 'OpenApiActivity'
};
};
passport.use('ihealth',myStrategy);
For Microsoft ADFS OAuth 2, this can be used to add the required source parameter; if one wants the callback to include some specific value too, then add the state parameter.
The options in function(options) can be set when calling passport.authenticate:
router.get('/auth', passport.authenticate('ihealth', {time: Date.now()}));
In this time I managed to find a workaround. Maybe it will help someone with a similar problem.
For the solution I didn't use the well known modules such as passport-oauth2 or simple-oauth2, but just the modules querystring for building the request URL and the module request for making the HTTP calls.
Example:
var express = require('express');
var router = express.Router();
var request = require('request');
var qs = require('querystring');
var configAuth = require('../config/auth');
var authorization_url_site = configAuth.iHealthAuth.authorizationSite;
var authorization_url_params = {
response_type : 'code',
client_id: configAuth.iHealthAuth.clientID,
redirect_uri: configAuth.iHealthAuth.callbackURL,
APIName : configAuth.iHealthAuth.APIName
};
var authorization_uri = authorization_url_site + '?' + qs.stringify(authorization_url_params);
var token_url_site = configAuth.iHealthAuth.tokenSite;
var token_url_params = {
grant_type : 'authorization_code',
client_id: configAuth.iHealthAuth.clientID,
client_secret: configAuth.iHealthAuth.clientSecret,
redirect_uri: configAuth.iHealthAuth.callbackURL,
code: req.query.code
};
var token_uri = token_url_site + '?' + qs.stringify(token_url_params);
// Initial page redirecting to the login page
router.route('/auth')
.get(function (req, res) {
res.redirect(authorization_uri);
});
// Callback service parsing the authorization token and asking for the access token
router.route('/')
.get(function(req, res) {
request(token_uri, function(err, response, body) {
if(err) {
throw err;
} else {
var data = JSON.parse(body);
// save token to database or file
saveToken(data);
}
});
});
});
module.exports = router;
Has anyone successfully navigated Jawbone's OAuth2.0 authentication for their REST API?
I am unable to figure out how to access and send the authorization_code in order to obtain the access_token (steps 4 & 5 in the Jawbone API Authorization Documentation). I want to reuse the access_token for subsequent (AJAX-style) calls and avoid asking the user to reauthorize each time.
Each call of the API (get.sleeps) requires a full round trip of the auth process including this reauthorization to get an authorization_token (screen shot). Both the Jawbone and Passport Documentation is vague on this point.
My stack involves, node.js, the jawbone-up NPM, express.js and passport.js. The Passport Strategy for Jawbone appears to work correctly as I get valid data back.
The jawbone-up NPM explicitly does not help maintain the session (access_token), saying "This library does not assist in getting an access_token through OAuth..."
QUESTION: how do I actually use the OAUTH access_token in the API call? Can someone show me some code to do this?
Thanks
var dotenv = require('dotenv').load(),
express = require('express'),
app = express(),
ejs = require('ejs'),
https = require('https'),
fs = require('fs'),
bodyParser = require('body-parser'),
passport = require('passport'),
JawboneStrategy = require('passport-oauth').OAuth2Strategy,
port = 5000,
jawboneAuth = {
clientID: process.env.JAWBONE_CLIENT_ID,
clientSecret: process.env.JAWBONE_CLIENT_SECRET,
authorizationURL: process.env.JAWBONE_AUTH_URL,
tokenURL: process.env.JAWBONE_AUTH_TOKEN_URL,
callbackURL: process.env.JAWBONE_CALLBACK_URL
},
sslOptions = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt')
};
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// ----- Passport set up ----- //
app.use(passport.initialize());
app.get('/',
passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
})
);
app.get('/done',
passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
}), function(req, res) {
res.render('userdata', req.account);
}
);
passport.use('jawbone', new JawboneStrategy({
clientID: jawboneAuth.clientID,
clientSecret: jawboneAuth.clientSecret,
authorizationURL: jawboneAuth.authorizationURL,
tokenURL: jawboneAuth.tokenURL,
callbackURL: jawboneAuth.callbackURL
}, function(token, refreshToken, profile, done) {
var options = {
access_token: token,
client_id: jawboneAuth.clientID,
client_secret: jawboneAuth.clientSecret
},
up = require('jawbone-up')(options);
up.sleeps.get({}, function(err, body) {
if (err) {
console.log('Error receiving Jawbone UP data');
} else {
var jawboneData = JSON.parse(body).data;
console.log(jawboneData);
return done(null, jawboneData, console.log('Jawbone UP data ready to be displayed.'));
}
});
}));
// HTTPS
var secureServer = https.createServer(sslOptions, app).listen(port, function(){
console.log('UP server listening on ' + port);
});
You weren't too far off, you were already getting the token. To make your code work a few steps are needed:
Add the concept of a "session", data that exists from request to request as a global variable. When you do a full web app use express-sessions and passport-sessions and implement user management. But for now we just add a global for a single user state.
var demoSession = {
accessToken: '',
refreshToken: ''
};
Pass in a user object in the done() of JawboneStrategy. This is because the "authorize" feature of passport is expecting a user to exist in the session. It attaches the authorize results to this user. Since we are just testing the API just pass in an empty user.
// Setup the passport jawbone authorization strategy
passport.use('jawbone', new JawboneStrategy({
clientID: jawboneAuth.clientID,
clientSecret: jawboneAuth.clientSecret,
authorizationURL: jawboneAuth.authorizationURL,
tokenURL: jawboneAuth.tokenURL,
callbackURL: jawboneAuth.callbackURL
}, function(accessToken, refreshToken, profile, done) {
// we got the access token, store it in our temp session
demoSession.accessToken = accessToken;
demoSession.refreshToken = refreshToken;
var user = {}; // <-- need empty user
done(null, user);
console.dir(demoSession);
}));
Use a special page to show the data "/data". Add a route to separate the auth from the display of service.
app.get('/done', passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
}), function(req, res) {
res.redirect('/data');
}
);
Lastly the Jawbone Up sleeps API is a little tricky. you have to add a YYYYMMDD string to the request:
app.get('/data', function(req, res) {
var options = {
access_token: demoSession.accessToken,
client_id: jawboneAuth.clientID,
client_secret: jawboneAuth.clientSecret
};
var up = require('jawbone-up')(options);
// we need to add date or sleep call fails
var yyyymmdd = (new Date()).toISOString().slice(0, 10).replace(/-/g, "");
console.log('Getting sleep for day ' + yyyymmdd);
up.sleeps.get({date:yyyymmdd}, function(err, body) {
if (err) {
console.log('Error receiving Jawbone UP data');
} else {
try {
var result = JSON.parse(body);
console.log(result);
res.render('userdata', {
requestTime: result.meta.time,
jawboneData: JSON.stringify(result.data)
});
}
catch(err) {
res.render('userdata', {
requestTime: 0,
jawboneData: 'Unknown result'
});
}
}
});
});
I have created a gist that works for me here thats based on your code: https://gist.github.com/longplay/65056061b68f730f1421
The Jawbone access token expires in 1 year so you definitely don't need to re-authenticate the user each time. Also you are provided with a refresh_token as well, so you can refresh the access token when needed.
Once you have the access_token you have to store it somewhere, preferably in some sort of a database or a file storage for later use, then you use that token for each request made to the Jawbone REST API.
The jawbone-up module uses request internally, so I'm going to show you how to make a request with it (it should be pretty much the same with any other module).
Here is how you can get the user's profile (the most basic API call):
var request = require('request')
request.get({
uri:'https://jawbone.com/nudge/api/v.1.1/users/#me',
auth:{bearer:'[ACCESS_TOKEN]'},
json:true
}, function (err, res, body) {
// body is a parsed JSON object containing the response data
})
There is another module called Purest which also uses request internally, but hides some of the complexity around using a REST API. Here is how the same request would look like using that module:
var Purest = require('purest')
var jawbone = new Purest({provider:'jawbone'})
jawbone.get('users/#me', {
auth:{bearer:'[ACCESS_TOKEN]'}
}, function (err, res, body) {
// body is a parsed JSON object containing the response data
})
Alternatively for authenticating the user (getting the access_token) you can use another module called Grant which I personally use, but either one should work.
I want to make a Google and Facebook authentication on my application. I am using AngularJS on my front-end and node.js on my server.
Here is my Facebook authentication function on my node.js server:
var User = require('./user.js'),
request = require('request'),
env = process.env.NODE_ENV = process.env.NODE_ENV || 'development',
config = require('./config.js')[env],
createSendToken = require('./jwt.js'),
qs = require('querystring');
module.exports = function (req, res) {
var accessTokenUrl = 'https://graph.facebook.com/oauth/access_token';
var graphApiUrl = 'https://graph.facebook.com/me';
var params = {
client_id: req.body.clientId,
redirect_uri: req.body.redirectUri,
client_secret: config.facebookAuth.clientSecret,
code: req.body.code
};
console.log(req.body.code);
request.get({
url: accessTokenUrl,
qs: params
}, function (err, response, accessToken) {
accessToken = qs.parse(accessToken);
request.get({
url: graphApiUrl,
qs: accessToken,
json: true
}, function (err, response, profile) {
User.findOne({
facebookId: profile.id // <-- fails here
}, function (err, existingUser) {
if (existingUser)
return createSendToken(existingUser, res);
var newUser = new User();
newUser.facebookId = profile.id;
newUser.displayName = profile.name;
newUser.email = profile.email;
newUser.save(function (err) {
createSendToken(newUser, res);
})
})
});
});
}
Then assigns to this post:
app.post('/auth/facebook', facebookAuth);
Front-end is working correctly ( I'm using satellizer ).
I did almost the same thing with Google+ and it works fine but with Facebook it isn't.
My Facebook app is configured correctly ( I have added http://localhost:3030/ to website URL and mobile website URL ).
Now when I run my app and press Facebook button, I get the following error on the server:
facebookId: profile.id
^
TypeError: Cannot read property 'id' of undefined
So that means that profile is undefined:
}, function (err, response, profile) {
Why did Facebook returned me an undefined profile?
How can I fix that?
Check the value of err in
json: true
}, function (err, response, profile) {
User.findOne({
facebookId: profile.id // <-- fails here
to answer your question