Search code examples
javascriptnode.jsaxiospassport.jspassport-local

How to Form Authentication Header for Axios Request to Node.js App Using Passport Local Authentication?


I have a node.js app and am developing a separate single page app (that will eventually be converted into Android and iOS native apps). I'm setting up an API on the node.js app and am struggling with authentication. The node.js app is using passport-local-mongoose for authentication and I store user data in a MongoDB backend. For testing/dev, the single page app is running on http://localhost:1234/.

My endpoint looks like:

exports.getDevicesAPI = async (req, res) => {
  res.header('Access-Control-Allow-Origin', req.headers.origin);
  res.header('Access-Control-Allow-Methods', 'GET, POST');
  res.header('Access-Control-Allow-Headers: Authorization');
  const devices = await Device.find({ owner: req.user._id });
  res.json(devices);
};

I can GET this no problem with something like:

const axios = require('axios');
const url = 'http://localhost:7777/api/devices';

function getDevices() {
  axios
    .get(url)
    .then(function(response) {
      console.log(response);
    })
    .catch(function(error) {
      console.log(error);
    });
}

I want to add authenticate = passport.authenticate('header', {session: false, failWithError: true}); on the server side to provide authentication, but the following gives me Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:7777/api/devices. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing):

const axios = require('axios');    
const url = 'http://localhost:7777/api/devices';

const username = myUsername;
const password = myPassword;

const axiosConfig = {
  headers: {
    'Content-Type': 'application/json',
  },
  Authorization: {
    username,
    password,
  },
};

function authenticate() {
  axios
    .post(url, axiosConfig)
    .then(function(response) {
      console.log('Authenticated');
    })
    .catch(function(error) {
      console.log('Error on Authentication');
    });
}

Routes (for testing):

router.get('/api/devices', catchErrors(deviceController.getDevicesAPI));
router.post('/api/devices', catchErrors(deviceController.getDevicesAPI));

What am I missing?


Solution

  • You are having issues with CORS(Cross-Origin Resource Sharing) Restrictions. Read more about CORS here.

    I believe this part of your code is meant to handle the CORS:

    exports.getDevicesAPI = async (req, res) => {
      // ...
      res.header('Access-Control-Allow-Origin', req.headers.origin);
      res.header('Access-Control-Allow-Methods', 'GET, POST');
      res.header('Access-Control-Allow-Headers: Authorization');
      // ...
    };
    
    

    However, the mistake here is that the setting of these CORS headers is tied to a route, i.e the getDevicesAPI route which is not supposed to be. For requests that are likely to modify resources in another origin(e.g the POST to getDevicesAPI route), the browser would first send a preflight request with the OPTIONS Http method before sending the actual request, the response to the preflight request is where the necessary CORS response-headers is expected to be set. You can find explanations on preflight requests here.

    I would typically add a middleware like this above the other routes:

    router.all('*', (req, res, next) => {
      res.header('Access-Control-Allow-Origin', '*');
      res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
      res.header('Access-Control-Allow-Headers', '*');
      next();
    });