Search code examples
node.jsswagger-3.0express-openapi-validator

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,
      });
    });
  });

Solution

  • 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.