HelmetJS and SwaggerUI: Avoiding headaches in your NodeJS app
Tags: javascript, security, documentation, community
Published: 2023-03-04 20:44:49

As a Software Engineer, the importance of embracing Security-driven development cannot be overemphasized. If you realize how important this is, then you must have used one or more security middlewares such as HelmetJS. These tools work great until you introduce another such as Swagger UI to aid with API documentation.

I have encountered some of these issues and thought it'd be helpful putting some tips out there so you don't end up commenting out `app.use(helmet())` at best 🙂

## Tips

1. Always make sure to render the Swagger UI template before setting up the HelmetJS middleware to avoid issues like this:
![Bunch of errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xq4uglt32ulxr4say4u1.png)
    
2. In most online references, this is how you set up a route to serve Swagger UI
    
    ```javascript
    const express = require('express');
    const swaggerUi = require('swagger-ui-express');
    const swaggerDocument = require('./swagger.json');
         
    const app = express();
         
    app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
    ```
    
    This would cause issues in your application as there's a difference between using `app.use` and an Express application routing method such as `app.get`. The former is used for requiring middleware and would match all routes that stem from the input route (tears if the input is `/`) irrespective of the HTTP verb. You'll find yourself sending POST requests to specific endpoints and getting back the HTML page of Swagger, not a good experience!
    
3. If you are going to be rendering some HTML page for some reason, ensure you use a templating engine such as ejs or pug to avoid getting screamed at like so:
   
![Content-Security-Policy error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3kh68xdazieszb1d6z3o.png)

This happens because HelmetJS sets a couple of security headers by default, one of these headers is the `Content-Security-Policy` header which has a default value of `default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests` . This header aims to help mitigate Cross-Site-Scripting (XSS) attacks amongst other things and "safe" inline scripts need to have a SHA256 hash or a nonce to enable execution.

To fix this, we will configure a templating engine (ejs in this case), generate a hash using the crypto package, and then set the `script-src` directive to `'self' 'nonce-{hash}'` like so:

```javascript
...
const crypto = require("crypto");

app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomBytes(16).toString('hex');
  next();
});

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      scriptSrc: [(req, res) => `'nonce-${res.locals.cspNonce}'`],
    },
  })
);
```

After setting the nonce, open up your ejs file and add set the nonce value on the script tag like so:

```xml
<script nonce="<%= cspNonce %>">
      const formElem = document.querySelector('#formElem');

      formElem.onsubmit = async (e) => {
        e.preventDefault();
        let url = window.location.href;
...
```

**NB:** Do ensure the variable names match, also, If you're going to be including external scripts such as Axios via CDN, ensure you add the nonce to the script tag as well

Thanks for reading and I hope it does help you out, would update this as I encounter more issues. See ya at the next one! 💖

## Further reading

1. [Swagger UI documentation](https://www.npmjs.com/package/swagger-ui-express)
    
2. [HelmetJS documentation](https://helmetjs.github.io/)