Clean Instagram oauth using node.js and express and minimal middlewares - node.js

I am trying to get a clean Instagram oauth without relying on middlewares such as passport, or instagram-node to learn the process and have maximum control. I have been trying to follow instagram Server-side (Explicit) Flow, which is a 2 step operation:
request an access code
request an access token
right now my server is set up using:
express = require('express'),
app = express();
and to initiate the first step I am using :
app.get('/', function(req, res){
var url = 'https://api.instagram.com/oauth/authorize/?client_id='+CLIENT-ID+'&redirect_uri='+YOUR-REDIRECT-URI+'&response_type=code'
res.redirect(url);
});
The above step sends me properly to instagram for authentication and the redirect callback of instagram gets picked up bellow at which point the console.log does display the correct instagram code. But the res.set part is wrong and does not work.
app.get('/auth/instagram/callback', function(req, res){
console.log('/// here to keep track of how many times this is called');
console.log('Instagram code: ', req.query.code);
var url = 'https://api.instagram.com/oauth/access_token';
res.set({
'client_id' : 'CLIENT-ID',
'client_secret' : 'CLIENT-SECRET',
'grant_type' : 'authorization_code',
'redirect_uri' : 'YOUR-REDIRECT-URI',
'code' : req.query.code
}).redirect(url);
});
Unfortunately it hangs at this point and clearly does not provide back the right data back.
Instagram suggest to do the following, but I am unsure how this would translate in express:
curl \-F 'client_id=CLIENT-ID' \
-F 'client_secret=CLIENT-SECRET' \
-F 'grant_type=authorization_code' \
-F 'redirect_uri=YOUR-REDIRECT-URI' \
-F 'code=CODE' \https://api.instagram.com/oauth/access_token
Any insight on this would be most welcome!
Thank you for your help.

And here is the actual response for the second part of OAuth with Instagram! Might not
var data = {'client_id' : process.env.FANCRAWLCLIENTID,
'client_secret' : process.env.FANCRAWLCLIENTSECRET,
'grant_type' : 'authorization_code',
'redirect_uri' : process.env.INSURIREDIRECT,
'code' : req.query.code
};
// Configure the request
var options = {
uri: 'https://api.instagram.com/oauth/access_token',
method: 'POST',
form: data
}
request(options, function (error, response, body) {
// to convert the string body to a usable object
var pbody = JSON.parse(body);
// pbody should look like this:
// {"access_token":"8943851.83434d.697342341324jkfdjsf41afd784932a2e8",
// "user":
// {"username":"my_user_name",
// "bio":"blah blah...",
// "website":"http:\/\/www.something.com",
// "profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_851_73sq_115.jpg",
// "full_name":"Full Name",
// "id":"8943851"}
// }
});
Enjoy!!!

I would suggest studying passport code (and instagram in particular).
In any case, after getting the code back (which works for you), you need to send a request from your backend code to Instagram. So your code would look more like (top of my head):
app.get('/auth/instagram/callback', function(req, res){
console.log('/// here to keep track of how many times this is called');
console.log('Instagram code: ', req.query.code);
var data = {
'url': url
'client_id' : 'CLIENT-ID',
'client_secret' : 'CLIENT-SECRET',
'grant_type' : 'authorization_code',
'redirect_uri' : 'YOUR-REDIRECT-URI',
'code' : req.query.code
};
var url = 'https://api.instagram.com/oauth/access_token';
request.post({
method: 'POST',
url: url,
body: JSON.stringify(data),
},
function (e, r, body) {
//body will contain the access_token
});
});
Then after you get the token you can set session, etc.

Ok got it to work to do post request for specific API calls but not yet the OAUTH part.. and WITH instagram secure header.
This exemple is to follow a user when you have an access token for a user.
var crypto = require('crypto'),
request = require('request');
var hmac = crypto.createHmac('SHA256', 'INSTAGRAM_CLIENT_ID');
hmac.setEncoding('hex');
hmac.write('IP_ADDRESS_127.0.0.1_OR_12.34.56.78');
hmac.end();
var hash = hmac.read();
// Set the headers
var headers = {
'X-Insta-Forwarded-For': 'IP_ADDRESS_127.0.0.1_OR_12.34.56.78|'+hash
}
// Configure the request
var options = {
uri: 'https://api.instagram.com/v1/users/1234/relationship_ OR WHATEVER API CALL',
qs: {'access_token': 'INSTAGRAM ACCESS TOKEN'},
method: 'POST',
headers: headers,
form:{action:'follow'}
}
request(options, function (error, response, body) {
// body response is what you are interested in
// NOTE that the body info is a string response so use var your_variable = JSON.parse(body) to use it as an object.
// Some exemples bellow
// USER NOT EXISTANT
// {"meta":{"error_type":"APINotFoundError","code":400,"error_message":"this user does not exist"}}
//
// successful response from unfollow
// {"meta":{"code":200},"data":{"outgoing_status":"none","target_user_is_private":false}}
//
// NOT FOLLOWING OR FOLLOWED BY
// {"meta":{"code":200},"data":{"outgoing_status":"none","target_user_is_private":false,"incoming_status":"none"}}
//
// you are following user 1234 but not followed back by them
// {"meta":{"code":200},"data":{"outgoing_status":"follows","target_user_is_private":false,"incoming_status":"none"}}
//
// Following and followed by
// {"meta":{"code":200},"data":{"outgoing_status":"follows","target_user_is_private":true,"incoming_status":"followed_by"}}
//
// PRIVATE users
// {"meta":{"code":200},"data":{"outgoing_status":"requested","target_user_is_private":true}}
});
I hope this helps.

Related

React/Node: Spotify API Error: 404 - No Active Device Found

I've created an app which works for Spotify Premium users only (PUT methods don't work for non-premium users according to Spotify's documentation). It's a ten-question interactive quiz where a playlist generates in your Spotify account, plays it and you have to guess the name of each song. It's generated with a NodeJS Backend and displayed via ReactJS. The game can be demoed here: https://am-spotify-quiz.herokuapp.com/
Code can be reviewed below:
server.js
const express = require('express');
const request = require('request');
const cors = require('cors');
const querystring = require('querystring');
const cookieParser = require('cookie-parser');
const client_id = ''; // Hiding for now
const client_secret = ''; // Hiding
const redirect_uri = 'https://am-spotify-quiz-api.herokuapp.com/callback/';
const appUrl = 'https://am-spotify-quiz.herokuapp.com/#';
/**
* Generates a random string containing numbers and letters
* #param {number} length The length of the string
* #return {string} The generated string
*/
var generateRandomString = function(length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
app.use(express.static(__dirname + '/public'))
.use(cors())
.use(cookieParser());
app.get('/login', function(req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// scopes needed to make required functions work
var scope = 'user-read-private ' +
'user-read-email ' +
'user-read-playback-state ' +
'user-top-read ' +
'playlist-modify-public ' +
'playlist-modify-private ' +
'user-modify-playback-state ' +
'user-read-playback-state';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
app.get('/callback/', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state === null || state !== storedState) {
res.redirect(appUrl +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: {
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json' // May not need
},
body: { // Likely don't need this anymore!
'name': 'Test Playlist',
'public': false
},
json: true
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect(appUrl +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect(appUrl +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
// AM - May not even need this anymore!
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
console.log('Listening on 8888');
app.listen(process.env.PORT || 8888);
I have a react component which displays as soon as the user is logged in, called premium.js. If you need all the code, you can see it here. Below are the two PUT methods that I need for my game; one to turn off the shuffle feature and the other one used to play the playlist:
removeShuffle() {
axios({
url: 'https://api.spotify.com/v1/me/player/shuffle?state=false',
method: "PUT",
headers: {
'Authorization': 'Bearer ' + this.state.accesstoken
}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
// Then... play the playlist to get started
playPlaylist(contextUri) {
axios({
url: 'https://api.spotify.com/v1/me/player/play',
method: "PUT",
data: {
context_uri: contextUri
},
headers: {
'Authorization': 'Bearer ' + this.state.accesstoken
}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
These work perfectly fine when I, the creator of the game, try it; however, I had another premium user try it and found this error:
This doesn't seem to make much sense as I've discovered this error happens with another user, regardless of whether they are using Windows or Mac. Does anyone know what this means, and how can I solve? Thanks in advance!
I've also been using Spotify's API and I eventually got the same error when trying to PUT https://api.spotify.com/v1/me/player/play after an inactivity period, where no device was marked as active (I don't know exactly how long, but no more than a couple of hours).
Apparently one device must be set as active so that you can invoke the play endpoint successfully.
If you want to change the status of a device as active, according to their documentation, you can first try to GET https://api.spotify.com/v1/me/player/devices in order to obtain the list of available devices:
// Example response
{
"devices" : [ {
"id" : "5fbb3ba6aa454b5534c4ba43a8c7e8e45a63ad0e",
"is_active" : false,
"is_private_session": true,
"is_restricted" : false,
"name" : "My fridge",
"type" : "Computer",
"volume_percent" : 100
} ]
}
and then select one of the available devices by invoking the player endpoint PUT https://api.spotify.com/v1/me/player, including:
device_ids Required. A JSON array containing the ID of the device on which playback should be started/transferred.
For example: {device_ids:["74ASZWbe4lXaubB36ztrGX"]}
Note: Although an array is accepted, only a single device_id is currently supported. Supplying more than one will return 400 Bad Request
play with value true if you want to start playing right away.
Most likely you didn't get that error yourself because one of your devices was already active when you tested it. If you have no activity during a couple of hours on your own account and then try to invoke the v1/me/player/play endpoint, I'd expect you to get the same error.
An easy workaround to make sure that this was in fact your problem would be to ask your test user to please start playing a song on the Spotify app (no matter which), then pause it, and then trigger the function on your app that invokes the v1/me/player/play endpoint. That shouldn't return the No active device found error anymore.
The way I understand it is you are trying to play a playlist that does not belong to the current user (/me) Which could be the cause of the error.

In Node.js, why doesn't the request package perform basic authentication?

I have set up a test end point here:
https://9wi46s5jzc.execute-api.us-east-1.amazonaws.com/test
For some baseline testing, I go to apitester.com and run two tests:
Firstly, a post request to: https://admin:password#9wi46s5jzc.execute-api.us-east-1.amazonaws.com/test (correct credentials) gives me output of:
{"isBase64Encoded":false,"statusCode":401,"headers":{"x-powered-by":"Express","content-type":"text/html; charset=utf-8","content-length":"0","etag":"W/\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\""},"body":""}
Secondly, a post request to: https://admin:BADPASSWORD#9wi46s5jzc.execute-api.us-east-1.amazonaws.com/test (incorrect credentials) gives me output of:
{"message":"Unauthorized"}
So they're the baseline tests for what should happen.
When I run the following code:
const request = require('request');
const url = 'https://admin:password#9wi46s5jzc.execute-api.us-east-1.amazonaws.com/test';
request.post(url, function(err, res, body) {
console.log("body", body);
});
I get:
body {"message":"Unauthorized"}
Why is this happening?
According to the docs:
https://github.com/request/request
this is the way to do basic authentication.
So I'm expecting correct authorization but I'm not getting it. What am I doing wrong?
You should try it using this :
const proxyUrl = 'https://admin:password#9wi46s5jzc.execute-api.us-east-1.amazonaws.com/test';
const proxyRequest = request.defaults({ 'proxy': proxyUrl});
const options = {
url: '...',
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + token // if this is how you handle auth and you already have the token
}
};
proxyRequest .get(options, function (error, response, body) {
if (error) {
next(error); // if you're using Express
}
console.log("body", body);
});

Passing the data from the Express API Parameter to Request Module in MEAN Stack

I am facing below issue with passing the parameters from the Express API to Request module URL.
In the below code Suppose I have the request details as
request_data.url = http://localhost:3000/interface/en/
When users enters the URL as http://localhost:3000/interface/en/123456
I wanted to send the 123456 to the line
url: request_data.url + acct,
Hence my final url for the request module becomes as http://localhost:3000/interface/en/123456
But my below code is not working , can someone help me here or suggest me what changes are requires
Code
app.get('/interface/:env/:acct', (req, res) => {
var acct = req.params.acct;
var env = req.params.env;
var hsResponse = request({
proxy: proxyUrl,
url: request_data.url + acct,
headers: request_data.headers,
method: request_data.method,
form: oauth.authorize(request_data)
}, function (error, response, body) {
res.setHeader('Content-Type', 'application/json');
res.send(body); //<-- send hsResponse response body back to your API consumer
});
});
Please use following code,
app.get('/interface/:env/:acct', (req, res) => {
var acct = req.params.acct;
var env = req.params.env;
// here you need to update your url
request_data.url = request_data.url + acct;
var hsResponse = request({
proxy: proxyUrl,
url: request_data.url ,
headers: request_data.headers,
method: request_data.method,
form: oauth.authorize(request_data)
}, function (error, response, body) {
res.setHeader('Content-Type', 'application/json');
res.send(body); //<-- send hsResponse response body back to your API consumer
});
});
I think you are using OAuth, where you passing form field to the request, which will need to authorize with existing mapped request_data like URL and other attributes.
Hope this will help you !!

Nodejs twitter api 403

I am trying to use the twitter api with nodejs 5.4.1, using the twitter api as a guide. Initially my bearer access token appears to be generated properly, though when I run the actuall request I keep getting a '403 Forbidden' error message. Any idea why this is?
var R = require("request");
var stream = require('twitter');
var https = require('https');
var key = 'my-key';
var secret = 'my-secret';
var cat = key +":"+secret;
var credentials = new Buffer(cat).toString('base64');
var url = 'https://api.twitter.com/oauth2/token';
R({ url: url,
method:'POST',
headers: {
"Authorization": "Basic " + credentials,
"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8"
},
body: "grant_type=client_credentials"
}, function(err, resp, body) {
var an = JSON.parse(body);
console.log( an['access_token']);
runIt(an['access_token']);
console.dir(body); //the bearer token...
});
function runIt(key){
var options = {
host: 'api.twitter.com',
path: '/1.1/users/search.json?q=Twitter%20API&page=1&count=3',
headers: {
'Host': 'api.twitter.com',
'Authorization' : 'Bearer ' + key,
'Accept-Encoding': 'gzip'
}
};
https.get(options,(res)=>{
console.log(res.statusCode);
console.log(res);
});
}
For Twitter User Api you'll have to follow the proper oauth steps to get things work properly.
Initialy there will be 2-step request process that will leave you with token and secret of user.
You will use that information to sign request with method like HMAC-SHA1 so that you can access data from twitter, node-auth can be helpful in this step. Twitter Link - Authorizing requests
For further understanding see these tutorials:
Implement Twitter Sign
Nodejs Twitter api
and for code inspiration:
Twitter Streamming | A NodeJS Component

Node.js and twilio integration

I am trying to integrate twilio with Node.js+express.
I don't have a site yet. what value should I give for HOSTNAME, along with SID and AUTH_TOKEN, these values I got from twilio site.
I have written some code, whatever suggestion given below I have placed in to views folder in twiclient.js , I have added a route in app.js to redirect the request if /twi is called , but I am not getting any result. some errors are appearing in the console, would you please help me figure out what I'm doing wrong? I have placed the correct SID, token and hostname, as specified below.
app.js has the following entry, does anything else need to be done for the twilio calling part to work?
Also, where should I define the GUI for calling a phone in the views folder?
var TwilioClient = require('twilio').Client,
      Twiml = require('twilio').Twiml,
      sys = require('sys');
var client = new TwilioClient('MY_ACCOUNT_SID', 'MY_AUTH_TOKEN', 'MY_HOSTNAME');
var phone = client.getPhoneNumber('+2323232323');
phone.setup(function() { phone.makeCall('+15555555555', null, function(call) {});
phone.setup(function() {
    phone.makeCall('+15555555555', null, function(call) {
        call.on('answered', function(callParams, response) {
            response.append(new Twiml.Say('Hey buddy. Let\'s meet for drinks later tonight.'));
            response.send();
        });
    });
});
The hostname is 'api.twilio.com'. Your SID and AUTH_TOKEN come from your twilio account. When you log in, go to the dashboard. You'll find your SID and AUTH_TOKEN listed there.
Here's the code I use to make a request to twilio to place a call. It should help you get started.
var https = require('https');
var qs = require('querystring');
var api = 'your api key';
var auth = 'your auth token';
var postdata = qs.stringify({
'From' : '+5554321212',
'To' : '+5552226262',
'Url' : 'http://yourwebsite.com/call'
});
var options = {
host: 'api.twilio.com',
path: '/2010-04-01/Accounts/<your api key>/Calls.xml',
port: 443,
method: 'POST',
headers: {
'Content-Type' : 'application/x-www-form-urlencoded',
'Content-Length' : postdata.length
},
auth: api + ':' + auth
};
var request = https.request(options, function(res){
res.setEncoding('utf8');
res.on('data', function(chunk){
console.log('Response: ' + chunk);
})
})
request.write(postdata);
request.end();

Resources