matthew-andrews / isomorphic-fetch

Isomorphic WHATWG Fetch API, for Node & Browserify
MIT License
6.95k stars 289 forks source link

How to format headers for api call #112

Closed StephenGrable1 closed 7 years ago

StephenGrable1 commented 7 years ago

I am currently trying to get a oath token (Bearer key for twitter's application-only authentication) from twitter's api. You can check out the docs here -> https://dev.twitter.com/oauth/application-only

I'm using react/redux, express/node and isomorphic-fetch to call the api but I am getting a response of 404. Twitters doc's say that if the page is responding with a 404 then the request is probably not formatted correctly.

After that I went into the console and checked the network request. I see all of the correctly formatted requests but my "Authorization" header is no where to be found. I'm wondering if I am formatting the api call correctly. I couldn't find any good resources on formatting headers for api calls for isomorphic-fetch. Here is my actions.jsx file where I am making the call

//actions.jsx file

   import fetch from 'isomorphic-fetch';

//this is my action 
   export var fetchTwtBearerToken = () => {
     return dispatch => {

    //Here I am just formatting my keys 
    var urlEncodeConsumerKey = encodeURI(process.env.TWT_CONSUMER_KEY);
    var urlEncodeConsumerSec = encodeURI(process.env.TWT_CONSUMER_SECRET);
    var bearerToken = urlEncodeConsumerKey + ':' + urlEncodeConsumerSec;
    var base64BearerToken = btoa(bearerToken);
    console.log(base64BearerToken);

      // Here is where I start the call 

    return fetch('https://api.twitter.com/oauth2/token', {
      method:'post',
      headers: {
        'Authorization': 'Basic ' + base64BearerToken,
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      body: 'grant_type=client_credentials',
      mode: 'no-cors'
    })
      .then(response => response.json().then(json => ({
        status: response.status,
        response: response,
        json
      })
    ))
      .then(({status, json}) => {
        if (status >= 400) {
          console.log(status, response, 'bad fetching');
        } else {
          console.log('good fetching');
        }
      })

  };
};

I'm desperately trying to find out if I am formatting the headers correctly or where I am going wrong here. Any help in the right direction is very much appreciated.

martinhorsky commented 7 years ago

@StephenGrable1 I have the same issue. Did you solve it?

StephenGrable1 commented 7 years ago

hey @martinhorsky I closed the issue because I decided to actually start making api calls from my server (node/express) instead of from redux. This way is more secure in the sense that my api keys are being concealed by using them on the server instead of using them on the client side (aka from redux).

I haven't revisited the Twitter api example but I have it working with facebook's graph api Which doesn't require a bearer token... but this is an example of a step in the right direction if you want to securely access api's.

Here is how i am doing it

// this is my actions.jsx file 

import fetch from 'isomorphic-fetch';

export var fetchFbFollowCount = (fbID, id) => {
  return dispatch => {
   // below tacks on /api/ + the fbID to the current url (I grab the fbID off of the url on the server side)
    return fetch('/api/'+fbID, {
      method: 'GET', 
      body: '',                   // This is how you would format the calls with a body and headers but 
     headers:{}                   // I am only concerned with receiving total followers of Facebook public pages
    })                            // so I don't need to provide anything in these fields
      .then(response => response.json().then(json => ({
        status: response.status,
        json
      })
    ))
      .then(({status, json}) => {
        if (status >= 400) {
          console.log(status, 'bad fetching');
        } else {
          console.log('good fetching');
        }
      })

  };
};
// this is my server.js file 

var express = require('express');
var envFile = require('node-env-file');
var path = require('path');
var request = require('request');

// below is grabbing my environment variables from a folder called config
//  and a file named development.env so I can use them in my call to the facebook api
try {
  envFile(path.join(__dirname, 'config/development.env'));
} catch (e) {
  console.log(e);
}

//create our app
var app =  express();
const PORT= process.env.PORT || 3000;

...

app.use(express.static('public'));

// Below I grab the call I made in redux (which is a url http://currentUrl.com/api/fbIDhere) 
// and pull the Facebook Id off of the url to be used to call facebook api like so

app.all("/api/:fbID", function(req,res){
  var fbID = req.params.fbID;
  var url = "https://graph.facebook.com/v2.8/" + fbID + `/?fields=name,fan_count&access_token=${process.env.FB_APP_ID}|${process.env.FB_APP_SECRET}`;

  request.get(url, function(error,response,body) {
    if (!error && response.statusCode == 200){
     // res.send sends the response from express -> back into redux 
      res.send(body);
    } else {
      console.log(error);
    }
  })

});

app.listen(PORT, function() {
  console.log('express is up on port ' + PORT);
});

Here is what my development.env file would look like for those who are new to this

FB_APP_ID=place your id number here without quotes like below 
FB_APP_SECRET=03axsb112323aad23262f9d5b5 (not a valid secret)

I plan on implementing this with the twitter api but this is working with facebook. It is a lot better than calling the api from within redux because again someone could easily get your api keys if you make the call from the client side.

You can check out my full project here where you can check the whole flow of calling the action from user input and returning api information https://github.com/StephenGrable1/ArtistTracker

Hope this helps @martinhorsky !!

martinhorsky commented 7 years ago

@StephenGrable1 Thanks, it helps.

StephenGrable1 commented 7 years ago

Hey @martinhorsky just figured out how to get the bearer token from twitter using react/redux and express. I was running into an issue with CORS which I have fixed. Here is the breakdown

It first starts with the action being called

//this is in action.jsx
import fetch from 'isomorphic-fetch';

export var fetchTwtBearerToken = () => {
  return dispatch => {
    return fetch('/twtapi/')
    .then(response => response.json().then(json => ({
      status: response.status,
      json
    })
  ))
    .then(({status, json}) => {
      if (status >= 400) {
        console.log(status, 'bad fetching');
      } else {
        console.log('good fetching', json);
      }
    })

  };
};

The express server is listening to the current url + /twtapi/. Once the action above calls that url the express server is run

// this is the server.js file 
var request = require('request');
var express = require('express');

...

//CORS middleware to avoid CORS issues 
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type');

    next();
}

app.use(allowCrossDomain);

//Twitter Api call
app.all("/twtapi/", function(req,res){

// below is creating the base64 encoded token
  var urlEncodeConsumerKey = encodeURI(process.env.TWT_CONSUMER_KEY);
  var urlEncodeConsumerSec = encodeURI(process.env.TWT_CONSUMER_SECRET);
  var bearerToken = urlEncodeConsumerKey + ':' + urlEncodeConsumerSec;
  var base64BearerToken = new Buffer(bearerToken).toString('base64');

  var options = {
    url: "https://api.twitter.com/oauth2/token",
    method:'POST',
    headers: {
       Authorization: 'Basic ' + base64BearerToken,
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    body: 'grant_type=client_credentials'
  }

  request.post(options, function(error,response,body) {
    if (!error && response.statusCode == 200){
      res.send(body);
      console.log(body, response, error);
    } else {
      console.log(error);
    }
  })
});

This will return the token back into actions.jsx and then you can use that token to make a call to the twitter api. This technique is only good for application-only authentication. https://dev.twitter.com/oauth/application-only. But since I am only worried about pulling the follower count off of public accounts this works perfect for me.

Cheers!