Comparing version
@@ -0,1 +1,18 @@ | ||
1.10.0 / 2019-04-22 | ||
=================== | ||
* deps: csrf@3.1.0 | ||
- Remove `base64-url` dependency | ||
- deps: tsscmp@1.0.6 | ||
- deps: uid-safe@2.1.5 | ||
* deps: http-errors@~1.7.2 | ||
- Make `message` property enumerable for `HttpError`s | ||
- Set constructor name when possible | ||
- deps: depd@~1.1.2 | ||
- deps: inherits@2.0.3 | ||
- deps: setprototypeof@1.1.1 | ||
- deps: statuses@'>= 1.5.0 < 2' | ||
* perf: remove argument reassignment | ||
* perf: use plain object for internal cookie options | ||
1.9.0 / 2016-05-27 | ||
@@ -2,0 +19,0 @@ ================== |
29
index.js
@@ -152,7 +152,8 @@ /*! | ||
var opts = { | ||
key: '_csrf', | ||
path: '/' | ||
} | ||
var opts = Object.create(null) | ||
// defaults | ||
opts.key = '_csrf' | ||
opts.path = '/' | ||
if (options && typeof options === 'object') { | ||
@@ -205,3 +206,2 @@ for (var prop in options) { | ||
if (!bag) { | ||
/* istanbul ignore next: should never actually run */ | ||
throw new Error('misconfigured csrf') | ||
@@ -252,3 +252,2 @@ } | ||
var header = Array.isArray(prev) ? prev.concat(data) | ||
: Array.isArray(data) ? [prev].concat(data) | ||
: [prev, data] | ||
@@ -273,20 +272,12 @@ | ||
// set secret on cookie | ||
var value = val | ||
if (cookie.signed) { | ||
var secret = req.secret | ||
if (!secret) { | ||
/* istanbul ignore next: should never actually run */ | ||
throw new Error('misconfigured csrf') | ||
} | ||
val = 's:' + sign(val, secret) | ||
value = 's:' + sign(val, req.secret) | ||
} | ||
setCookie(res, cookie.key, val, cookie) | ||
} else if (req[sessionKey]) { | ||
setCookie(res, cookie.key, value, cookie) | ||
} else { | ||
// set secret on session | ||
req[sessionKey].csrfSecret = val | ||
} else { | ||
/* istanbul ignore next: should never actually run */ | ||
throw new Error('misconfigured csrf') | ||
} | ||
@@ -293,0 +284,0 @@ } |
{ | ||
"name": "csurf", | ||
"description": "CSRF token middleware", | ||
"version": "1.9.0", | ||
"version": "1.10.0", | ||
"author": "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)", | ||
@@ -14,17 +14,20 @@ "contributors": [ | ||
"cookie-signature": "1.0.6", | ||
"csrf": "~3.0.3", | ||
"http-errors": "~1.5.0" | ||
"csrf": "3.1.0", | ||
"http-errors": "~1.7.2" | ||
}, | ||
"devDependencies": { | ||
"body-parser": "1.15.1", | ||
"connect": "3.4.1", | ||
"cookie-parser": "1.4.3", | ||
"cookie-session": "~1.1.0", | ||
"eslint": "2.10.2", | ||
"eslint-config-standard": "5.3.1", | ||
"eslint-plugin-promise": "1.3.1", | ||
"eslint-plugin-standard": "1.3.2", | ||
"istanbul": "0.4.3", | ||
"mocha": "2.5.3", | ||
"supertest": "1.1.0" | ||
"body-parser": "1.18.3", | ||
"connect": "3.6.6", | ||
"cookie-parser": "1.4.4", | ||
"cookie-session": "1.3.3", | ||
"eslint": "5.16.0", | ||
"eslint-config-standard": "12.0.0", | ||
"eslint-plugin-import": "2.16.0", | ||
"eslint-plugin-markdown": "1.0.0", | ||
"eslint-plugin-node": "8.0.1", | ||
"eslint-plugin-promise": "4.1.1", | ||
"eslint-plugin-standard": "4.0.0", | ||
"mocha": "6.1.4", | ||
"nyc": "14.0.0", | ||
"supertest": "4.0.2" | ||
}, | ||
@@ -35,6 +38,6 @@ "engines": { | ||
"scripts": { | ||
"lint": "eslint **/*.js", | ||
"lint": "eslint --plugin markdown --ext js,md .", | ||
"test": "mocha --check-leaks --reporter spec --bail test/", | ||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --check-leaks --reporter dot test/", | ||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --check-leaks --reporter spec test/" | ||
"test-cov": "nyc --reporter=html --reporter=text npm test", | ||
"test-travis": "nyc --reporter=text npm test" | ||
}, | ||
@@ -41,0 +44,0 @@ "files": [ |
113
README.md
# csurf | ||
[![NPM Version][npm-image]][npm-url] | ||
[![NPM Downloads][downloads-image]][downloads-url] | ||
[![NPM Version][npm-version-image]][npm-url] | ||
[![NPM Downloads][npm-downloads-image]][node-url] | ||
[![Build status][travis-image]][travis-url] | ||
[![Test coverage][coveralls-image]][coveralls-url] | ||
[![Gratipay][gratipay-image]][gratipay-url] | ||
@@ -25,2 +24,6 @@ Node.js [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection middleware. | ||
This is a [Node.js](https://nodejs.org/en/) module available through the | ||
[npm registry](https://www.npmjs.com/). Installation is done using the | ||
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): | ||
```sh | ||
@@ -32,2 +35,4 @@ $ npm install csurf | ||
<!-- eslint-disable no-unused-vars --> | ||
```js | ||
@@ -68,4 +73,12 @@ var csurf = require('csurf') | ||
- `path` - the path of the cookie (defaults to `'/'`). | ||
- any other [res.cookie](http://expressjs.com/4x/api.html#res.cookie) | ||
option can be set. | ||
- `signed` - indicates if the cookie should be signed (defaults to `false`). | ||
- `secure` - marks the cookie to be used with HTTPS only (defaults to | ||
`false`). | ||
- `maxAge` - the number of seconds after which the cookie will expire | ||
(defaults to session length). | ||
- `httpOnly` - flags the cookie to be accessible only by the web server | ||
(defaults to `false`). | ||
- `sameSite` - sets the same site policy for the cookie (defaults to none). | ||
- `domain` - sets the domain the cookie is valid on (defaults to current | ||
domain). | ||
@@ -127,3 +140,3 @@ ##### ignoreMethods | ||
app.get('/form', csrfProtection, function(req, res) { | ||
app.get('/form', csrfProtection, function (req, res) { | ||
// pass the csrfToken to the view | ||
@@ -133,3 +146,3 @@ res.render('send', { csrfToken: req.csrfToken() }) | ||
app.post('/process', parseForm, csrfProtection, function(req, res) { | ||
app.post('/process', parseForm, csrfProtection, function (req, res) { | ||
res.send('data is being processed') | ||
@@ -152,2 +165,66 @@ }) | ||
#### Using AJAX | ||
When accessing protected routes via ajax both the csrf token will need to be | ||
passed in the request. Typically this is done using a request header, as adding | ||
a request header can typically be done at a central location easily without | ||
payload modification. | ||
The CSRF token is obtained from the `req.csrfToken()` call on the server-side. | ||
This token needs to be exposed to the client-side, typically by including it in | ||
the initial page content. One possibility is to store it in an HTML `<meta>` tag, | ||
where value can then be retrieved at the time of the request by JavaScript. | ||
The following can be included in your view (handlebar example below), where the | ||
`csrfToken` value came from `req.csrfToken()`: | ||
```html | ||
<meta name="csrf-token" content="{{csrfToken}}"> | ||
``` | ||
The following is an example of using the | ||
[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to post | ||
to the `/process` route with the CSRF token from the `<meta>` tag on the page: | ||
<!-- eslint-env browser --> | ||
```js | ||
// Read the CSRF token from the <meta> tag | ||
var token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') | ||
// Make a request using the Fetch API | ||
fetch('/process', { | ||
credentials: 'same-origin', // <-- includes cookies in the request | ||
headers: { | ||
'CSRF-Token': token // <-- is the csrf token as a header | ||
}, | ||
method: 'POST', | ||
body: { | ||
favoriteColor: 'blue' | ||
} | ||
}) | ||
``` | ||
#### Single Page Application (SPA) | ||
Many SPA frameworks like Angular have CSRF support built in automatically. | ||
Typically they will reflect the value from a specific cookie, like | ||
`XSRF-TOKEN` (which is the case for Angular). | ||
To take advantage of this, set the value from `req.csrfToken()` in the cookie | ||
used by the SPA framework. This is only necessary to do on the route that | ||
renders the page (where `res.render` or `res.sendFile` is called in Express, | ||
for example). | ||
The following is an example for Express of a typical SPA response: | ||
<!-- eslint-disable no-undef --> | ||
```js | ||
app.all('*', function (req, res) { | ||
res.cookie('XSRF-TOKEN', req.csrfToken()) | ||
res.render('index') | ||
}) | ||
``` | ||
### Ignoring Routes | ||
@@ -184,3 +261,3 @@ | ||
app.get('/form', function(req, res) { | ||
app.get('/form', function (req, res) { | ||
// pass the csrfToken to the view | ||
@@ -190,10 +267,10 @@ res.render('send', { csrfToken: req.csrfToken() }) | ||
app.post('/process', function(req, res) { | ||
app.post('/process', function (req, res) { | ||
res.send('csrf was required to get here') | ||
}) | ||
function createApiRouter() { | ||
function createApiRouter () { | ||
var router = new express.Router() | ||
router.post('/getProfile', function(req, res) { | ||
router.post('/getProfile', function (req, res) { | ||
res.send('no csrf to get here') | ||
@@ -237,11 +314,9 @@ }) | ||
[npm-image]: https://img.shields.io/npm/v/csurf.svg | ||
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/csurf/master | ||
[coveralls-url]: https://coveralls.io/r/expressjs/csurf?branch=master | ||
[node-url]: https://nodejs.org/en/download | ||
[npm-downloads-image]: https://badgen.net/npm/dm/csurf | ||
[npm-url]: https://npmjs.org/package/csurf | ||
[travis-image]: https://img.shields.io/travis/expressjs/csurf/master.svg | ||
[npm-version-image]: https://badgen.net/npm/v/csurf | ||
[travis-image]: https://badgen.net/travis/expressjs/csurf/master | ||
[travis-url]: https://travis-ci.org/expressjs/csurf | ||
[coveralls-image]: https://img.shields.io/coveralls/expressjs/csurf/master.svg | ||
[coveralls-url]: https://coveralls.io/r/expressjs/csurf?branch=master | ||
[downloads-image]: https://img.shields.io/npm/dm/csurf.svg | ||
[downloads-url]: https://npmjs.org/package/csurf | ||
[gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg | ||
[gratipay-url]: https://gratipay.com/dougwilson/ |
23093
14.03%314
31.38%14
27.27%240
-3.61%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated