0

I have set up my front-end with an interceptor that always adds an Authorization header if a JWT token exist.

I have 2 API's, one is used to check authorization, the other one is used to handle any other data request (this is the one I'm working on).

What I want to achieve in my data API is the following:

Create some kind of guard(?) which I can place around certain api routes which checks if a Authorization header is present. Then it needs to do a call to a different API to check if the token is valid. If this fails, it returns an error, otherwise it continues to do the wanted request.

I 'm new to node, and I don't know the correct and most efficient way to do this. I don't have enough knowledge, so I tried googling it, but with no result.

I'm not asking you guys to write this for me, I'm just looking for ideas on how to do this, so I can research it more in depth, because right now I don't know what to look for or if this is even possible.

Thanks for your help!

EDIT: Here's how I currently handle a request

Route

  /**
   * Add survey for a participant
   *
   * URL: /participant/survey
   * METHOD: POST
   */
  router.post('/participant/survey', function(req, res) {
    var bodyValidation = iValidator.json_schema(
      schema.addSurvey,
      req.body,
      'survey'
    );
    if (bodyValidation.valid == false) {
      return res.status(422).send(bodyValidation.errorMessage);
    }
    participantService
      .addSurvey(req.body)
      .then(data => {
        res.status(200).send({ success: true, survey: data });
      })
      .catch(err => {
        res.status(422).send(err);
      });
  });

Service

function addSurvey(survey) {
  return new Promise((resolve, reject) => {
    participantModel
      .addSurvey(survey)
      .then(data => {
        survey.id = data.insertId;
        resolve(survey);
      })
      .catch(err => {
        reject(err);
      });
  });
}

Model

function addSurvey(survey) {
  return new Promise((resolve, reject) => {
    db.query(
      'INSERT INTO ...',
      (error, result) => {
        if (error) {
          dbFunc.connectionRelease;
          reject(error);
        } else {
          dbFunc.connectionRelease;
          resolve(result);
        }
      }
    );
  });
}
Nicolas
  • 4,526
  • 17
  • 50
  • 87
  • 1
    Hi! Please read through the [help], in particular [*How do I ask a good question?*](/help/how-to-ask) It will make it a **lot** easier for people to help you if you show us a [mcve] of one of the requests where you need to do this, and an example of the authorization check code. – T.J. Crowder Aug 08 '18 at 12:09
  • 1
    @T.J.Crowder Thanks for explaining how I can improve my question. I added some code to make it more clear – Nicolas Aug 08 '18 at 12:17
  • What does the authorization code look like? – T.J. Crowder Aug 08 '18 at 12:21
  • Side note: The service exhibits the [`Promise` constructor anti-pattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). You don't need, or want, `new Promise` in it; you *already* have a promise, from `addSurvey`. Just return the result of chaining on it: https://pastebin.com/c2UmdNh5 – T.J. Crowder Aug 08 '18 at 12:21
  • @T.J.Crowder I don't have access to that code – Nicolas Aug 08 '18 at 12:30
  • 1
    We don't need to see the internals, we need to know how you *call* it to get authorization. But in any case, the two answers you already have should be sufficient to let you figure this out. – T.J. Crowder Aug 08 '18 at 12:31

2 Answers2

2

For node your express API routes are processed as first come first serve. So typically all of the end points you want to expose publicly are defined first, followed by any routes which are protected by some auth middleware.

So in your case what you could do is have a catch all route defined. The first method would check the Authorization header is present, if it is you call next() and Node JS will go on to check the next route, or if no token if found you can reject the request there and then.

After your Authorization function you could check your API for the validity of the token. And apply the same logic here. You might want to consider caching this response depending on the overhead/latency.

app.use((req, res, next) => {
    if(<token-exists-condition>){
        if(<token-valid-condition>){
            // Pass through your middleware onto the next route
            next();
        } else {
            res.status(401).send('Invalid token');
        }
    } else {
        res.status(401).send('No Auth Header');
    }
});

Reference the express docs for more detail on how to use middleware.

Adam
  • 1,724
  • 13
  • 16
1

While this would work, you're probably better off using a middleware function instead as Adam shows in his answer. Using middleware functions like that is more composable, letting you focus on each route's specific logic in the routes without worrying about the authorization part. Simply apply the middleware to the relevant routes.


Assuming your route handlers follow the standard pattern:

router.get('/relevant/path', function (req, res) {
  res.send(/*...response...*/);
});

...then you just wait to call res.send until you've had a response to your authorization check. You haven't shown us that check, but in semi-pseudocode:

// If it provides a promise
router.get('/relevant/path', function (req, res) {
  checkAuthorization(/*...*/)
      .then(result => {
          if (result.authorized) {
              res.send(/*...response...*/);
          } else {
              res.send(/*...'not authorized' response...*/);
          }
      })
      .catch(error => {
          res.send(/*...error response, auth check failed...*/);
      });
});

or

// If it provides a Node.js-style callback
router.get('/relevant/path', function (req, res) {
  checkAuthorization((error, result) => {
      if (error) {
          res.send(/*...error response, auth check failed...*/);
      } else {
          if (result.authorized) {
              res.send(/*...response...*/);
          } else {
              res.send(/*...'not authorized' response...*/);
          }
      }
});
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875