1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Add with-strict-csp example (#4858)

This commit is contained in:
Thomas Hermann 2018-08-06 23:19:16 -04:00 committed by Tim Neutkens
parent e8aa78204a
commit 83970c908d
6 changed files with 147 additions and 0 deletions

View file

@ -0,0 +1,46 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-strict-csp)
# Strict CSP example
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
```bash
npx create-next-app --example with-strict-csp with-strict-csp-app
# or
yarn create next-app --example with-strict-csp with-strict-csp-app
```
### Download manually
Download the example:
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-strict-csp
cd with-strict-csp
```
Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
## The idea behind the example
If you want to implement a CSP, the most effective way is to follow the [strict CSP](https://csp.withgoogle.com/docs/strict-csp.html) approach. For it to work, we need to generate a nonce on every request.
This example uses [Helmet](https://github.com/helmetjs/helmet) to configure the CSP and add the appropriate headers to all server responses. The nonce is generated with [uuid](https://github.com/kelektiv/node-uuid). Then we can pass the nonce to `<Head>` and `<NextScript>` in the custom `<Document>`.

View file

@ -0,0 +1,31 @@
const helmet = require('helmet')
const uuidv4 = require('uuid/v4')
module.exports = function csp (app) {
// Create a nonce on every request and make it available to other middleware
app.use((req, res, next) => {
res.locals.nonce = Buffer.from(uuidv4()).toString('base64')
next()
})
const nonce = (req, res) => `'nonce-${res.locals.nonce}'`
const scriptSrc = [nonce, "'strict-dynamic'", "'unsafe-inline'", 'https:']
// In dev we allow 'unsafe-eval', so HMR doesn't trigger the CSP
if (process.env.NODE_ENV !== 'production') {
scriptSrc.push("'unsafe-eval'")
}
app.use(
helmet({
contentSecurityPolicy: {
directives: {
baseUri: ["'none'"],
objectSrc: ["'none'"],
scriptSrc
}
}
})
)
}

View file

@ -0,0 +1,17 @@
{
"name": "with-strict-csp",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"express": "^4.14.0",
"helmet": "3.13.0",
"next": "latest",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"uuid": "3.3.2"
}
}

View file

@ -0,0 +1,28 @@
import Document, { Head, Main, NextScript } from 'next/document'
const inlineScript = (body, nonce) => (
<script type='text/javascript' dangerouslySetInnerHTML={{ __html: body }} nonce={nonce} />
)
export default class MyDocument extends Document {
static async getInitialProps (ctx) {
const initialProps = await Document.getInitialProps(ctx)
const { nonce } = ctx.res.locals
return { ...initialProps, nonce }
}
render () {
const { nonce } = this.props
return (
<html>
<Head nonce={nonce}>
{inlineScript(`console.log('Inline script with nonce')`, nonce)}
</Head>
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</html>
)
}
}

View file

@ -0,0 +1,3 @@
export default () => (
<div>Hello World</div>
)

View file

@ -0,0 +1,22 @@
const express = require('express')
const next = require('next')
const csp = require('./csp')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
csp(server)
server.use(handle)
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})