Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A better way to check for authorization of API requests using Express JS

I have a super redundant server.js file, since nearly every method in it belonging to the REST API starts like that, as I have to check at the API requests whether the client is authorized to ask for the particular thing.

var jwt = require('jsonwebtoken');

// ...

app.get('/getsomething', function(req, res) {
  "use strict";
  var token = req.headers[tokenName];
  if (token) {
    jwt.verify(token, app.get('some_secret'), {
      ignoreExpiration: false
    }, function(err, decoded) {
      if (err || typeof decoded === "undefined") {
        res.json({
          status: 401,
          message: "unauthorized"
        });
      }
      else { // actual code starts here...

What would be a better way?

like image 430
tom Avatar asked Oct 07 '17 01:10

tom


People also ask

How does Express handle authorization?

After the user is logged in, a data request is sent by the client with a signed JWT token (to inform the server who is asking for data). On the server side we check if the provided JWT is valid, then we check if the user is allowed to see the data that was requested (this step is known as authorization).

Is Axios same as Express?

Axios is used to send a web request whereas express is used to listen and serve these web requests. In simple words, express is used to respond to the web requests sent by axios. If you know about the fetch() method in javascript, axios is just an alternative to fetch().

Why should I use passport js?

Passport is a popular, modular authentication middleware for Node. js applications. With it, authentication can be easily integrated into any Node- and Express-based app. The Passport library provides more than 500 authentication mechanisms, including OAuth, JWT, and simple username and password based authentication.


1 Answers

You should have a middleware that checks every request coming in, something like this:

// AUTHENTICATION
app.use(async (req) => {
    try {
        const token = req.headers.authorization
        const { person } = await jwt.verify(token, SECRET)
        req.person = person
        return req.next()
    } catch (e) {
        return req.next()
    }
})

In this example, both success or fail allows the user to pass through to the route, but req.person is only populated if the user is logged in. Keep in mind, this is an asynchronous function due to async keyword. It returns a promise and we are using try/catch architecture. This allows us to use await which halts execution until jwt.verify() has populated person. This then makes req.person available in every route. In this example, person is a property that you specified when you did jwt.sign() to create the JWT. Here is an example to jog your memory:

jwt.sign({
  person: 'some unique identifier'
}, 'secret', { expiresIn: '1y' })

In this way, req.person can only ever be populated by decoding a valid JWT. Do not be confused by const { person } = await jwt.verify(token, SECRET). This is the same as writing:

const decodedJWT = await jwt.verify(token, SECRET)
const person = decodedJWT.person

In every protected route, you can simply make the first line of code something like:

// PROTECTED
app.get('/radical', async (req, res, next) => {
    try {
        // If req.person is falsy, the user is not logged in
        if (!req.person) return res.status(403).render('error/403')
        // Otherwise, the user is logged in, allow him/her to continue
        // Replace res.render() with res.json() in your case.
        return res.render('radical/template', {
            person: req.person
        })
    } catch (e) {
        return next(e)
    }
})

This example demonstrates EJS View Templating Engine:

  • See: https://www.npmjs.com/package/ejs

Then, after all your routes, put a splat route, and an error handling middleware:

// SPLAT ROUTE
app.get('*', (req, res, next) => {
    return res.status(404).render('error/404')
})

// ERRORS
app.use((err, req, res, next) => {
    res.status(500).render('error/500')
    throw err
})

The error handling middleware needs to come last, and it has an additional 4th parameter which is err and it contains the value of the parameter of next() only if you pass a parameter to it, ie: next('Error happened').

This above code works without any changes for GraphQL as well. To handle authentication in GraphQL, simply examine this:

// GRAPHQL
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
    const context = {
        person: req.person
    }
    return {
        schema,
        context,
        rootValue: null,
        formatError: (error) => ({
            message: error.message,
            locations: error.locations,
            stack: error.stack,
            path: error.path
        }),
        debug: true
    }
}))

The GraphQL endpoint must be mounted after the authentication middleware. The logged in user will be available in every resolver as context.person, or if the request is illegal, context.person will be falsy. I mention this for any others searching in the future.

like image 157
agm1984 Avatar answered Oct 23 '22 12:10

agm1984