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

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


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

// AUTHENTICATIONapp.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:

// PROTECTEDapp.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:

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

// SPLAT ROUTEapp.get('*', (req, res, next) => {    return res.status(404).render('error/404')})// ERRORSapp.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:

// GRAPHQLapp.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.