Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #441 from Baroshem/feat/ssg-headers
feat(core): Security Headers for Pre-rendered Routes
- Loading branch information
Showing
48 changed files
with
2,678 additions
and
1,531 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# Nuxt Security Hooks | ||
|
||
Use hooks to further customize Nuxt Security | ||
|
||
--- | ||
|
||
:ellipsis{right=0px width=75% blur=150px} | ||
|
||
## Available hooks | ||
|
||
Nuxt Security provides two custom hooks: | ||
|
||
1. **The routeRules hook** (Nitro runtime hook): to modify the settings of Nuxt-Security at runtime. | ||
2. **The prerenderedHeaders hook** (Nuxt buildtime hook): to create a headers configuration file for your static server. | ||
|
||
## Route Rules Hook | ||
|
||
The `nuxt-security:routeRules` hook is convenient when you don't know the applicable security options at build time. | ||
|
||
This happens most frequently in the following cases: | ||
- Your Nuxt application is designed to be deployed on multiple websites, with a different configuration for each website. | ||
- Your security options are kept in a third-party vault system (e.g. Google Secret Manager), and your build system does not have access to the secrets. | ||
|
||
::alert{type="info"} | ||
Your `nuxt-security:routeRules` hook will be invoked each time your server is restarted. | ||
:: | ||
|
||
### How to use | ||
|
||
In order to use this hook, you will need to write a Nitro plugin | ||
|
||
```ts{}[/server/plugins/my-plugin.ts] | ||
export default defineNitroPlugin((nitroApp) => { | ||
nitroApp.hooks.hook('nuxt-security:routeRules', (appSecurityOptions) => { | ||
// Your code here | ||
}) | ||
}) | ||
``` | ||
|
||
- The `appSecurityOptions` variable contains all your application security options in the form of router definitions. | ||
|
||
These router definitions are provided in the form of h3's radix router rules. | ||
Please see [radix3](https://github.com/unjs/radix3) for further details. | ||
|
||
- The anonymous function `(appSecurityOptions) => {}` will be called asynchronously | ||
|
||
If you need to fetch your security data from an external secrets' manager API, you can use `async/await` in your code. | ||
|
||
- Your code can modify any security option inside this hook | ||
|
||
For each route, you can modify the rules exactly as you would do it with the `routeRules` option of `nuxt.config.ts`. | ||
|
||
|
||
### Examples | ||
|
||
You can apply custom settings to your whole application by modifying the base `/**` route : | ||
|
||
```ts | ||
export default defineNitroPlugin((nitroApp) => { | ||
nitroApp.hooks.hook('nuxt-security:routeRules', async(appSecurityOptions) => { | ||
const cspConnectSrc = await $fetch('https://secret-manager-api.com/api-route') | ||
// This example replaces only the connect-src CSP directive | ||
appSecurityOptions['/**'] = defuReplaceArray( | ||
{ | ||
headers: { | ||
contentSecurityPolicy: { | ||
"connect-src": [cspConnectSrc] | ||
} | ||
} | ||
}, | ||
appSecurityOptions['/**'] | ||
) | ||
}) | ||
}) | ||
``` | ||
|
||
You can also apply your settings to selected sub-routes of your application : | ||
|
||
```ts | ||
export default defineNitroPlugin((nitroApp) => { | ||
nitroApp.hooks.hook('nuxt-security:routeRules', async(appSecurityOptions) => { | ||
const cspConnectSrc = await $fetch('https://secret-manager-api.com/api-route') | ||
// This example modifies the CSP only for `/admin/**` routes | ||
appSecurityOptions['/admin/**'] = defuReplaceArray( | ||
{ | ||
headers: { | ||
contentSecurityPolicy: { | ||
"connect-src": [cspConnectSrc] | ||
} | ||
} | ||
}, | ||
appSecurityOptions['/admin/**'] | ||
) | ||
}) | ||
}) | ||
``` | ||
|
||
You are not constrained to CSP options, you can modify any security option with this hook : | ||
|
||
```ts | ||
export default defineNitroPlugin((nitroApp) => { | ||
nitroApp.hooks.hook('nuxt-security:routeRules', async(appSecurityOptions) => { | ||
const tokenLimit = await $fetch('https://secret-manager-api.com/api-route') | ||
// This example modifies the Rate Limiter only for API routes | ||
// It also modifies the X-Powered-By setting for these routes | ||
appSecurityOptions['/api/**'] = defuReplaceArray( | ||
{ | ||
rateLimiter: { | ||
tokensPerInterval: tokenLimit | ||
}, | ||
hidePoweredBy: false | ||
}, | ||
appSecurityOptions['/api/**'] | ||
) | ||
}) | ||
}) | ||
``` | ||
|
||
## Prerendered Headers Hook | ||
|
||
The `nuxt-security:prerenderedHeaders` hook is convenient when you want to know the security headers that should be delivered by your static server. | ||
|
||
This happens most frequently when you deploy your website statically on a CDN. In that case, your server has the HTML pages, but it doesn't know which security headers should be delivered. | ||
|
||
You may want to configure your hosting provider so that the correct headers are delivered for each static page. | ||
|
||
|
||
::alert{type="info"} | ||
Your `nuxt-security:prerenderedHeaders` hook will be invoked each time your build your application. | ||
:: | ||
|
||
### How to use | ||
|
||
In order to use this hook, you will need write your code in `defineNuxtConfig` | ||
|
||
```ts{}[nuxt.config.ts] | ||
export default defineNuxtConfig({ | ||
hooks: { | ||
'nuxt-security:prerenderedHeaders': (prerenderedHeaders) => { | ||
// Your code here | ||
} | ||
} | ||
}) | ||
``` | ||
|
||
- The `prerenderedHeaders` variable contains all calculated headers for each page. | ||
|
||
```js | ||
{ | ||
'/page1': { | ||
header1: value1, | ||
header2: value2 | ||
}, | ||
'/page2': { | ||
header3: value3, | ||
... | ||
} | ||
} | ||
``` | ||
|
||
- The anonymous function `(prerenderedHeaders) => {}` will be called asynchronously | ||
|
||
If you need to write to files asynchronously in your code, you can use `async/await` in your code. | ||
|
||
|
||
### Examples | ||
|
||
You can generate nginx-compatible header rules within your CI/CD pipeline and save them to a file on disk : | ||
|
||
```ts{}[nuxt.config.ts] | ||
import { writeFile } from 'node:fs/promises' | ||
defineNuxtConfig({ | ||
hooks: { | ||
'nuxt-security:prerenderedHeaders': async(prerenderedHeaders) => { | ||
// Don't take this snippet for granted, this is just provided as a basic example | ||
let nginxText = '' | ||
for (const path in prerenderedHeaders) { | ||
nginxText += 'location ' + path + ' {\n' | ||
const headersForPath = prerenderedHeaders[path] | ||
for (const headerName in headersForPath) { | ||
const headerValue = headersForPath[headerName] | ||
nginxText += ` add_header ${headerName} "${headerValue}";\n` | ||
} | ||
nginxText += '}\n\n' | ||
} | ||
await writeFile('./.nuxt/server.headers', nginxText) | ||
} | ||
} | ||
}) | ||
``` |
Oops, something went wrong.