Improve security

Adopt our methodology to gradually improve security.


Introduction

Whether you are a beginner or a security expert, you will always face the same issue: How can I secure my application without breaking it ?

Nuxt-Security provides a methodological approach to solve this problem :

  1. Start with default values: reasonable settings that will not break your application
  2. Fix common issues: in case you encounter issues, modify the required settings
  3. Then implement strict mode: Stricter settings that will require further customization for the application to work
The strict option is designed to help you gradually improve your application security.

This section is provided to walk you through our step-by-step approach to implementing the best security options for your application.

Start with default values

Out of the box, Nuxt Security applies a set of reasonable default values. Because strict: false is the default value, you don't need to do anything to apply these defaults.

Our tests show that these defaults will provide you with a perfect A+ score on the Mozilla Observatory, without breaking your application.

Our recommendation is that you start by installing Nuxt Security without modifying any of its default values.

Fix common issues

In some rare cases, some of your application features will be blocked by our default security settings. In order to fix these issues, launch your application in a browser and open the Developer Tools to inspect the errors in the console.

The most common issues usually fall into one these categories:

1. A script is blocked

In most cases, this is because you have included an external script in an 'unconventional' way, which is unrecognized by the Content Security Policy settings for the script-src directive.

Our recommendation is to always include external scripts via useScript and we provide a detailed section on Including External Scripts.

This scenario can also happen if your hosting provider messes with your code. See below.

2. An image or video is blocked

By default, Nuxt Security requires you to whitelist your external media sources. For security reasons, the Content Security Policy settings of the img-src directive only allows you to include self-hosted files.

If you need to include external media from third-party sites, please refer to our documentation on Whitelisting External Resources.

This scenario can also happen if your hosting provider messes with your code. See below.

3. A frame is blocked

In the vast majority of cases, this is due to Cross Origin Isolation restrictions on third-party frames. For maximum compatibility, Nuxt Security applies the credentialless policy to the COEP header. However, some third-party resources are incompatible with COEP/COOP requirements, which is beyond your control.

In that case, please follow our instructions on Cross-Origin Isolation Issues.

This scenario can also happen if your hosting provider messes with your code. See below.

4. Your hosting provider messes with your code

Some hosting providers will modify your code upon deployment. Usually this is intended to minify your files, or to inject trackers that provide additional services. By default, Nuxt Security will detect that your code has been modified and our Content Security Policy will block the application from running.

Please make sure that you disable all Post-Build Modification services that may be applied by your hosting platform. You can refer to our resources here:

5. Camera, microphone, etc. access is denied

By default, Nuxt Security sets Permissions Policies that protect your users against unintended image, sound, location or screen captures.

If your application requires using one of these features, please modify your Permission Policies.

6. SSL, TLS, and other HTTPS denials

You will face this issue if you are trying to connect to non-HTTPS resources. This happen because Nuxt Security makes sure that only secure, encrypted connections are allowed, via both its upgrade-insecure-requests Content Security Policy, and its strictTransportSecurity settings.

In general, you should not connect to non-HTTPS resources. If you need to disable SSL upgrading in development mode, we provide instructions on several standard use cases:

Improve gradually with 'strict' mode

Once your application is running smoothly in the default mode, you can start improving step-by-step by migrating to the strict mode.

To do so, modify the strict option of Nuxt Security:

nuxt.config.ts
export default defineNuxtConfig({
  // Global
  security: {
    strict: true
  }
})

The strict mode will apply much stricter settings to your application:

  • Content Security Policy: all directives are disallowed by default. You will need to manually whitelist each of the resources that you want to allow.
  • Permissions Policy: all features are disallowed. You will need to manually whitelist the features that you want to allow.
  • External frames: all external frames will be disallowed, and cross-origin isolation will be enforced.
  • SSL/HTTPS: HSTS will be enforced with preload, and the minimum period will be extended to 1 year.

Each of these modifications are listed below in order.

Enforcing a Stricter Content Security Policy

In strict mode, we apply the following defaults to CSP:

nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      contentSecurityPolicy: {
        'base-uri': ["'none'"],
        'default-src' : ["'none'"],
        'connect-src': ["'self'"],
        'font-src': ["'self'"],
        'form-action': ["'self'"],
        'frame-ancestors': ["'self'"],
        'frame-src': ["'self'"],
        'img-src': ["'self'"],
        'manifest-src': ["'self'"],
        'media-src': ["'self'"],
        'object-src': ["'none'"],
        'script-src-attr': ["'none'"],
        'style-src': ["'self'", "'nonce-{{nonce}}'"],
        'script-src': ["'self'", "'strict-dynamic'", "'nonce-{{nonce}}'"],
        'upgrade-insecure-requests': true,
        'worker-src': ["'self'"],
      }
    },
    ssg: {
      hashStyles: true
    }
  }
})

With these values, Content Security Policy applies the default-src: 'none' directive, which denies all resources by default. Also, it removes all 'unsafe-inline' fallbacks and migrates to inline style hashes.

As a consequence, you will need to manually whitelist the specific external resources that you know are legitimate:

  • If you connect to an external API backend, modify the connect-src directive
  • If you load external fonts, modify the font-src directive
  • If you use external stylesheets, modify the style-src directive
  • If you embed an external frame, modify the frame-src directive
  • etc...

The best way to go through this process is to open your browsers's Developer Tools and inspect the console errors. All the denied resources will be listed there, together with the name of the corresponding directive that needs to be modified.

Please note that by way of convenience, this setup does not block self-hosted resources. If you do not use self-hosted resources for one of the directives, you can further remove the 'self' keyword from that directive.

Enforcing a Stricter Permissions Policy

In strict mode, we apply the following defaults to Permissions:

nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      permissionsPolicy: {
        accelerometer: [],
        //'ambient-light-sensor':[],
        autoplay:[],
        //battery:[],
        camera:[],
        'display-capture':[],
        //'document-domain':[],
        'encrypted-media':[],
        fullscreen:[],
        //gamepad:[],
        geolocation:[],
        gyroscope:[],
        //'layout-animations':['self'],
        //'legacy-image-formats':['self'],
        magnetometer:[],
        microphone:[],
        midi:[],
        //'oversized-images':['self'],
        payment:[],
        'picture-in-picture':[],
        'publickey-credentials-get':[],
        'screen-wake-lock':[],
        //'speaker-selection':[],
        'sync-xhr':['self'],
        //'unoptimized-images':['self'],
        //'unsized-media':['self'],
        usb:[],
        'web-share':[],
        'xr-spatial-tracking':[]
      }
    }
  }
})

Please review each of these settings by inspecting your Developer Tools console and detecting which ones you might need to allow in the context of your application.

As a side note, you will notice that some of the values are commented away. This is because the corresponding features are still under Experimental status and are not yet widely implemented. You can still use them according to your use case.

Enforcing a Stricter Embedding Frame Policy

In strict mode, we apply the following defaults to external frames:

nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      contentSecurityPolicy: {
        'frame-src': ["'self'"]
      },
      crossOriginEmbedderPolicy: 'require-corp',
      xFrameOptions: 'DENY'
    }
  }
})

These settings enforce two additional security measures in relation to iframes:

  1. You will only be able to embed external frames that are individually whitelisted in the frame-src directive
  2. Any such frame will be cross-origin isolated, due to the application of the require-corp COEP value

Cross-origin isolation requires the embedded frame to be delivered with matching COEP/COOP headers. Your browser's Developer Tools will tell you if it's not the case. Please refer to Cross-Origin Isolation Issues for remediation solutions.

Enforcing a Stricter HSTS Policy

In strict mode, we apply the following defaults to HTTPS upgrades:

nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      strictTransportSecurity: {
        maxAge: 31536000,
        includeSubdomains: true,
        preload: true
      }
    }
  }
})

These settings allow you to submit your site to the Chrome HSTS preload list.

Please note that enabling HSTS preloading is not part of the official specification and may come with its own issues. For more information, see the MDN documentation