JWT verification when using API Gateway (Node / Express)

Viewed 385

Intro

My application is composed of 3 services:

  • Gateway: Handles all of the requests. Passes them to the appropriate service.
  • Authentication: Hands out JWT tokens stored in cookies for user login.
  • Shortener: Simple service that allows you to generate and retrieve shortened URLs.

Requests to '/auth' should be forwarded directly from the gateway to authService. The remaining requests are forwarded to the shortenService. Everything works fine as is. Here is some sample code:

const app = express();
const { createProxyMiddleware } = require('http-proxy-middleware');

const PORT = process.env.PORT || 4000;
const authService = createProxyMiddleware({ target: 'http://localhost:3001/'});
const shortenService = createProxyMiddleware({ target: 'http://localhost:3000/'});

app.use('/auth', authService);
app.use('/', shortenService);

app.listen(PORT, () => {
    console.log(`Server listening on port ${PORT}`)
});

Problem

My goal is to have all requests to the shortService first run through a function on my authService that verifies the JWT token. Also, I would like to set some values on my req object (ex: req.userId). Some solutions come to mind:

  1. Make the JWT key accessible to my Gateway and have the Gateway run the JWT verify command.
  2. On the Gateway, extract the JWT token from the cookie. Write an API on authService that accepts token as input and returns the decoded token as JSON. Have the Gateway use this API and then set the req object values on the Gateway using the returned JSON.
  3. Proxy all requests to authService and then let authService proxy requests to the shortenService.
  4. Move the authentication service to my Gateway.

I can think of issues for all of these. #1 means my JWT key is now on 2 different services. #2 seems weird. #3 defeats the purpose of having a Gateway. I'd rather avoid #4.

Is there an option where I could actually pass the req to the authService, allow the authService to run the decode AND to set the values on the req object, return to the Gateway, and then move on to the shortenService? Is this necessarily more desirable than #2?

For example, it would be great if this could work but the requests seem to terminate at my authService when I tried it out:

app.use('/', authService, shortenService);
1 Answers

Option #2 seems ideal, not sure why you'd call it weird. You could indeed have your Gateway use the authService as an API:

Gateway basically checks for the cookie (if there is none, no need to even contact authService), passes it on to authService, then adds the response in e.g. req.auth.

The http-proxy-middleware middleware allows you to modify the request first, e.g. add another header with the JSON representation of req.auth. On your other services (i.e. shortenService) you can add a quick middleware that will decode the header (if present) and assign it to req.auth.

This approach give all your (future) services the exact same req.auth data, while only the Gateway had to communicate with the authService. It also allows some other handy things, e.g. only allowing authenticated services to even send requests to some of your services.

Related