nkhil
nkhil

Reputation: 1620

How to add custom middleware to express-openapi-validator using Swagger 3

I've got a Node app using express-openapi-validator that takes a an api spec file (which is a .yml file), with request and response validation. The express-openapi-validator package routes the request to a handler file (defined in the spec). This is what one of the handlers might look like:

function getUsers(req, res) {
  const { 'x-user-id': userId } = req.headers
  res.status(200).json(`Your userId is ${userId}`)
}

I've got an API key feature, where users can get a new API key, and the other endpoints that need the caller to have the API key in the request headers to validate the request.

I know it should be possible to use middleware to validate the request, but I can't figure out how to use custom middleware with the express-openapi-validator package on select endpoints.

For eg:

GET /apikey = does not require api key GET /resource = requires api key

How do I configure this?

Here's what the openapi validator code in my app.js looks like:

new OpenApiValidator({
  apiSpec,
  validateResponses: true,
  operationHandlers: path.join(__dirname, './handlers'),
})
  .install(app)
  .then(() => {
    app.use((err, _, res) => {
      res.status(err.status || 500).json({
        message: err.message,
        errors: err.errors,
      });
    });
  });

Upvotes: 3

Views: 10999

Answers (3)

Giorgi Mamatelashvili
Giorgi Mamatelashvili

Reputation: 11

You can simply pass array of handlers instead of just 1 function, like in express. So in you code, the getUsers function that probably is what the x-eov-operation-id refers to, would be an array of 2 functions:

const getUsers = [
  apiKeyMiddleware,
  (req, res) => {
    const { 'x-user-id': userId } = req.headers
    res.status(200).json(`Your userId is ${userId}`)
  }
];

Upvotes: 1

nkhil
nkhil

Reputation: 1620

I actually ended up finding a solution for this myself.

First of all, I'm using version 4.10.5 of express-openapi-validator, so the code above is slightly different.

Here's what it looks like now:

// index.js
app.use(
    OpenApiValidator.middleware({
      apiSpec,
      validateResponses: true,
      operationHandlers: path.join(__dirname, './handlers'),
      validateSecurity: {
        handlers: {
          verifyApiKey(req, scopes) {
            return middleware.verifyApiKey(req)
          },
          bearerAuth(req, scopes) {
            return middleware.verifyToken(req)
          }
        }
      },
    }),
  );

  app.use((err, req, res, next) => {
    res.status(err.status || 500).json({
      message: err.message,
      errors: err.errors,
    });

The way I ended up using middleware in my routes is below:

I've added a securitySchemes section in my swagger.yml file, like so:

components:
  securitySchemes:
    verifyApiKey:
      type: apiKey
      in: header
      name: x-api-key
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

There's a bit more information about it here: https://swagger.io/docs/specification/authentication/

On each route that needs the middleware, I'm adding a security section, like so:

/team:
    post:
      security:
        - bearerAuth: []
      description: Create a new team
      operationId: createTeam
      x-eov-operation-id: createTeam
      x-eov-operation-handler: team

As you can see in my code above (in the index.js file), I've got a validateSecurity key, with a handlers key that then has the correlating keys that are in my swagger.yml (verifyApiKey and bearerAuth). These functions get the request and scope to check if they're valid. These functions return a boolean value, so true means that the middleware lets the request through, and false means a 403 response will be returned.

validateSecurity: {
        handlers: {
          verifyApiKey(req, scopes) {
            return middleware.verifyApiKey(req)
          },
          bearerAuth(req, scopes) {
            return middleware.verifyToken(req)
          }
        }
      },

Please respond if I've got anything above wrong, or if the explanation can be clearer. If you have questions, please post them below.

Upvotes: 5

zishone
zishone

Reputation: 1244

I was in a similar situation as you, using OpenAPI/Swagger packages like that limited my ability to add specific middleware per endpoint, so my solution was I created an npm module called @zishone/chaindler.

You can use it like this:

const { Chain } = require('@zishone/chaindler');

function getUsers(req, res) {
  const { 'x-user-id': userId } = req.headers
  res.status(200).json(`Your userId is ${userId}`)
}

function postUsers(req, res) {
  // ...
}

function mw1(req, res, next) {
  next()
}

function mw2(req, res, next) {
  next()
}

module.exports = {
  getUsers: new Chain(mw1, mw2).handle(getUsers),
  postUsers: new Chain(mw1).handle(postUsers)
}

Basically it just chains the middlewares then calls them one by one then call the handler/controller last.

Upvotes: -1

Related Questions