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.