├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── HISTORY.md ├── LICENSE ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - standard 4 | - plugin:markdown/recommended 5 | rules: 6 | no-param-reassign: error 7 | plugins: 8 | - markdown 9 | overrides: 10 | - files: '**/*.md' 11 | processor: 'markdown/markdown' 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | node_modules/ 3 | coverage/ 4 | npm-debug.log 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | unreleased 2 | ========== 3 | 4 | * deps: cookie@0.4.1 5 | - Fix `maxAge` option to reject invalid values 6 | * deps: http-errors@1.8.0 7 | - deps: setprototypeof@1.2.0 8 | 9 | 1.11.0 / 2020-01-18 10 | =================== 11 | 12 | * deps: cookie@0.4.0 13 | - Add `SameSite=None` support 14 | * deps: http-errors@~1.7.3 15 | - deps: inherits@2.0.4 16 | 17 | 1.10.0 / 2019-04-22 18 | =================== 19 | 20 | * deps: csrf@3.1.0 21 | - Remove `base64-url` dependency 22 | - deps: tsscmp@1.0.6 23 | - deps: uid-safe@2.1.5 24 | * deps: http-errors@~1.7.2 25 | - Make `message` property enumerable for `HttpError`s 26 | - Set constructor name when possible 27 | - deps: depd@~1.1.2 28 | - deps: inherits@2.0.3 29 | - deps: setprototypeof@1.1.1 30 | - deps: statuses@'>= 1.5.0 < 2' 31 | * perf: remove argument reassignment 32 | * perf: use plain object for internal cookie options 33 | 34 | 1.9.0 / 2016-05-27 35 | ================== 36 | 37 | * Pass invalid csrf token error to `next()` instead of throwing 38 | * Pass misconfigured error to `next()` instead of throwing 39 | * Provide misconfigured error when using cookies without cookie-parser 40 | * deps: cookie@0.3.1 41 | - Add `sameSite` option 42 | - Fix cookie `Max-Age` to never be a floating point number 43 | - Improve error message when `expires` is not a `Date` 44 | - Throw better error for invalid argument to parse 45 | - Throw on invalid values provided to `serialize` 46 | - perf: enable strict mode 47 | - perf: hoist regular expression 48 | - perf: use for loop in parse 49 | - perf: use string concatination for serialization 50 | * deps: csrf@~3.0.3 51 | - Use `tsscmp` module for timing-safe token verification 52 | - deps: base64-url@1.2.2 53 | - deps: rndm@1.2.0 54 | - deps: uid-safe@2.1.1 55 | * deps: http-errors@~1.5.0 56 | - Add `HttpError` export, for `err instanceof createError.HttpError` 57 | - Support new code `421 Misdirected Request` 58 | - Use `setprototypeof` module to replace `__proto__` setting 59 | - deps: inherits@2.0.1 60 | - deps: statuses@'>= 1.3.0 < 2' 61 | - perf: enable strict mode 62 | * perf: enable strict mode 63 | * perf: remove argument reassignment 64 | 65 | 1.8.3 / 2015-06-10 66 | ================== 67 | 68 | * deps: cookie@0.1.3 69 | - Slight optimizations 70 | 71 | 1.8.2 / 2015-05-09 72 | ================== 73 | 74 | * deps: csrf@~3.0.0 75 | - deps: uid-safe@~2.0.0 76 | 77 | 1.8.1 / 2015-05-03 78 | ================== 79 | 80 | * deps: csrf@~2.0.7 81 | - Fix compatibility with `crypto.DEFAULT_ENCODING` global changes 82 | 83 | 1.8.0 / 2015-04-07 84 | ================== 85 | 86 | * Add `sessionKey` option 87 | 88 | 1.7.0 / 2015-02-15 89 | ================== 90 | 91 | * Accept `CSRF-Token` and `XSRF-Token` request headers 92 | * Default `cookie.path` to `'/'`, if using cookies 93 | * deps: cookie-signature@1.0.6 94 | * deps: csrf@~2.0.6 95 | - deps: base64-url@1.2.1 96 | - deps: uid-safe@~1.1.0 97 | * deps: http-errors@~1.3.1 98 | - Construct errors using defined constructors from `createError` 99 | - Fix error names that are not identifiers 100 | - Set a meaningful `name` property on constructed errors 101 | 102 | 1.6.6 / 2015-01-31 103 | ================== 104 | 105 | * deps: csrf@~2.0.5 106 | - deps: base64-url@1.2.0 107 | - deps: uid-safe@~1.0.3 108 | 109 | 1.6.5 / 2015-01-08 110 | ================== 111 | 112 | * deps: csrf@~2.0.4 113 | - deps: uid-safe@~1.0.2 114 | 115 | 1.6.4 / 2014-12-30 116 | ================== 117 | 118 | * deps: csrf@~2.0.3 119 | - Slight speed improvement for `verify` 120 | - deps: base64-url@1.1.0 121 | - deps: rndm@~1.1.0 122 | * deps: http-errors@~1.2.8 123 | - Fix stack trace from exported function 124 | 125 | 1.6.3 / 2014-11-09 126 | ================== 127 | 128 | * deps: csrf@~2.0.2 129 | - deps: scmp@1.0.0 130 | * deps: http-errors@~1.2.7 131 | - Remove duplicate line 132 | 133 | 1.6.2 / 2014-10-14 134 | ================== 135 | 136 | * Fix cookie name when using `cookie: true` 137 | * deps: http-errors@~1.2.6 138 | - Fix `expose` to be `true` for `ClientError` constructor 139 | - Use `inherits` instead of `util` 140 | - deps: statuses@1 141 | 142 | 1.6.1 / 2014-09-05 143 | ================== 144 | 145 | * deps: cookie-signature@1.0.5 146 | 147 | 1.6.0 / 2014-09-03 148 | ================== 149 | 150 | * Set `code` property on CSRF token errors 151 | 152 | 1.5.0 / 2014-08-24 153 | ================== 154 | 155 | * Add `ignoreMethods` option 156 | 157 | 1.4.1 / 2014-08-22 158 | ================== 159 | 160 | * Use `csrf-tokens` instead of `csrf` 161 | 162 | 1.4.0 / 2014-07-30 163 | ================== 164 | 165 | * Support changing `req.session` after `csurf` middleware 166 | - Calling `res.csrfToken()` after `req.session.destroy()` will now work 167 | 168 | 1.3.0 / 2014-07-03 169 | ================== 170 | 171 | * Add support for environments without `res.cookie` (connect@3) 172 | 173 | 1.2.2 / 2014-06-18 174 | ================== 175 | 176 | * deps: csrf-tokens@~2.0.0 177 | 178 | 1.2.1 / 2014-06-09 179 | ================== 180 | 181 | * Refactor to use `csrf-tokens` module 182 | 183 | 1.2.0 / 2014-05-13 184 | ================== 185 | 186 | * Add support for double-submit cookie 187 | 188 | 1.1.0 / 2014-04-06 189 | ================== 190 | 191 | * Add constant-time string compare 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 Jonathan Ong <me@jongleberry.com> 4 | Copyright (c) 2014-2016 Douglas Christopher Wilson <doug@somethingdoug.com> 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > **This repository is archived and no longer actively maintained.** 3 | > 4 | > We are no longer accepting issues, feature requests, or pull requests. 5 | > For additional support or questions, please visit the [Express.js Discussions page](https://github.com/expressjs/express/discussions). 6 | 7 | 8 | # csurf 9 | 10 | [![NPM Version][npm-version-image]][npm-url] 11 | [![NPM Downloads][npm-downloads-image]][node-url] 12 | [![Build status][github-actions-ci-image]][github-actions-ci-url] 13 | [![Test coverage][coveralls-image]][coveralls-url] 14 | 15 | Node.js [CSRF][wikipedia-csrf] protection middleware. 16 | 17 | Requires either a session middleware or [cookie-parser](https://www.npmjs.com/package/cookie-parser) to be initialized first. 18 | 19 | * If you are setting the ["cookie" option](#cookie) to a non-`false` value, 20 | then you must use [cookie-parser](https://www.npmjs.com/package/cookie-parser) 21 | before this module. 22 | * Otherwise, you must use a session middleware before this module. For example: 23 | - [express-session](https://www.npmjs.com/package/express-session) 24 | - [cookie-session](https://www.npmjs.com/package/cookie-session) 25 | 26 | If you have questions on how this module is implemented, please read 27 | [Understanding CSRF](https://github.com/pillarjs/understanding-csrf). 28 | 29 | ## Installation 30 | 31 | This is a [Node.js](https://nodejs.org/en/) module available through the 32 | [npm registry](https://www.npmjs.com/). Installation is done using the 33 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 34 | 35 | ```sh 36 | $ npm install csurf 37 | ``` 38 | 39 | ## API 40 | 41 | ```js 42 | var csurf = require('csurf') 43 | ``` 44 | 45 | ### csurf([options]) 46 | 47 | Create a middleware for CSRF token creation and validation. This middleware 48 | adds a `req.csrfToken()` function to make a token which should be added to 49 | requests which mutate state, within a hidden form field, query-string etc. 50 | This token is validated against the visitor's session or csrf cookie. 51 | 52 | #### Options 53 | 54 | The `csurf` function takes an optional `options` object that may contain 55 | any of the following keys: 56 | 57 | ##### cookie 58 | 59 | Determines if the token secret for the user should be stored in a cookie 60 | or in `req.session`. Storing the token secret in a cookie implements 61 | the [double submit cookie pattern][owsap-csrf-double-submit]. 62 | Defaults to `false`. 63 | 64 | When set to `true` (or an object of options for the cookie), then the module 65 | changes behavior and no longer uses `req.session`. This means you _are no 66 | longer required to use a session middleware_. Instead, you do need to use the 67 | [cookie-parser](https://www.npmjs.com/package/cookie-parser) middleware in 68 | your app before this middleware. 69 | 70 | When set to an object, cookie storage of the secret is enabled and the 71 | object contains options for this functionality (when set to `true`, the 72 | defaults for the options are used). The options may contain any of the 73 | following keys: 74 | 75 | - `key` - the name of the cookie to use to store the token secret 76 | (defaults to `'_csrf'`). 77 | - `path` - the path of the cookie (defaults to `'/'`). 78 | - `signed` - indicates if the cookie should be signed (defaults to `false`). 79 | - `secure` - marks the cookie to be used with HTTPS only (defaults to 80 | `false`). 81 | - `maxAge` - the number of seconds after which the cookie will expire 82 | (defaults to session length). 83 | - `httpOnly` - flags the cookie to be accessible only by the web server 84 | (defaults to `false`). 85 | - `sameSite` - sets the same site policy for the cookie(defaults to 86 | `false`). This can be set to `'strict'`, `'lax'`, `'none'`, or `true` 87 | (which maps to `'strict'`). 88 | - `domain` - sets the domain the cookie is valid on(defaults to current 89 | domain). 90 | 91 | ##### ignoreMethods 92 | 93 | An array of the methods for which CSRF token checking will disabled. 94 | Defaults to `['GET', 'HEAD', 'OPTIONS']`. 95 | 96 | ##### sessionKey 97 | 98 | Determines what property ("key") on `req` the session object is located. 99 | Defaults to `'session'` (i.e. looks at `req.session`). The CSRF secret 100 | from this library is stored and read as `req[sessionKey].csrfSecret`. 101 | 102 | If the ["cookie" option](#cookie) is not `false`, then this option does 103 | nothing. 104 | 105 | ##### value 106 | 107 | Provide a function that the middleware will invoke to read the token from 108 | the request for validation. The function is called as `value(req)` and is 109 | expected to return the token as a string. 110 | 111 | The default value is a function that reads the token from the following 112 | locations, in order: 113 | 114 | - `req.body._csrf` - typically generated by the `body-parser` module. 115 | - `req.query._csrf` - a built-in from Express.js to read from the URL 116 | query string. 117 | - `req.headers['csrf-token']` - the `CSRF-Token` HTTP request header. 118 | - `req.headers['xsrf-token']` - the `XSRF-Token` HTTP request header. 119 | - `req.headers['x-csrf-token']` - the `X-CSRF-Token` HTTP request header. 120 | - `req.headers['x-xsrf-token']` - the `X-XSRF-Token` HTTP request header. 121 | 122 | ## Example 123 | 124 | ### Simple express example 125 | 126 | The following is an example of some server-side code that generates a form 127 | that requires a CSRF token to post back. 128 | 129 | ```js 130 | var cookieParser = require('cookie-parser') 131 | var csrf = require('csurf') 132 | var bodyParser = require('body-parser') 133 | var express = require('express') 134 | 135 | // setup route middlewares 136 | var csrfProtection = csrf({ cookie: true }) 137 | var parseForm = bodyParser.urlencoded({ extended: false }) 138 | 139 | // create express app 140 | var app = express() 141 | 142 | // parse cookies 143 | // we need this because "cookie" is true in csrfProtection 144 | app.use(cookieParser()) 145 | 146 | app.get('/form', csrfProtection, function (req, res) { 147 | // pass the csrfToken to the view 148 | res.render('send', { csrfToken: req.csrfToken() }) 149 | }) 150 | 151 | app.post('/process', parseForm, csrfProtection, function (req, res) { 152 | res.send('data is being processed') 153 | }) 154 | ``` 155 | 156 | Inside the view (depending on your template language; handlebars-style 157 | is demonstrated here), set the `csrfToken` value as the value of a hidden 158 | input field named `_csrf`: 159 | 160 | ```html 161 | <form action="/process" method="POST"> 162 | <input type="hidden" name="_csrf" value="{{csrfToken}}"> 163 | 164 | Favorite color: <input type="text" name="favoriteColor"> 165 | <button type="submit">Submit</button> 166 | </form> 167 | ``` 168 | 169 | #### Using AJAX 170 | 171 | When accessing protected routes via ajax both the csrf token will need to be 172 | passed in the request. Typically this is done using a request header, as adding 173 | a request header can typically be done at a central location easily without 174 | payload modification. 175 | 176 | The CSRF token is obtained from the `req.csrfToken()` call on the server-side. 177 | This token needs to be exposed to the client-side, typically by including it in 178 | the initial page content. One possibility is to store it in an HTML `<meta>` tag, 179 | where value can then be retrieved at the time of the request by JavaScript. 180 | 181 | The following can be included in your view (handlebar example below), where the 182 | `csrfToken` value came from `req.csrfToken()`: 183 | 184 | ```html 185 | <meta name="csrf-token" content="{{csrfToken}}"> 186 | ``` 187 | 188 | The following is an example of using the 189 | [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to post 190 | to the `/process` route with the CSRF token from the `<meta>` tag on the page: 191 | 192 | <!-- eslint-env browser --> 193 | 194 | ```js 195 | // Read the CSRF token from the <meta> tag 196 | var token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') 197 | 198 | // Make a request using the Fetch API 199 | fetch('/process', { 200 | credentials: 'same-origin', // <-- includes cookies in the request 201 | headers: { 202 | 'CSRF-Token': token // <-- is the csrf token as a header 203 | }, 204 | method: 'POST', 205 | body: { 206 | favoriteColor: 'blue' 207 | } 208 | }) 209 | ``` 210 | 211 | #### Single Page Application (SPA) 212 | 213 | Many SPA frameworks like Angular have CSRF support built in automatically. 214 | Typically they will reflect the value from a specific cookie, like 215 | `XSRF-TOKEN` (which is the case for Angular). 216 | 217 | To take advantage of this, set the value from `req.csrfToken()` in the cookie 218 | used by the SPA framework. This is only necessary to do on the route that 219 | renders the page (where `res.render` or `res.sendFile` is called in Express, 220 | for example). 221 | 222 | The following is an example for Express of a typical SPA response: 223 | 224 | ```js 225 | app.all('*', function (req, res) { 226 | res.cookie('XSRF-TOKEN', req.csrfToken()) 227 | res.render('index') 228 | }) 229 | ``` 230 | 231 | ### Ignoring Routes 232 | 233 | **Note** CSRF checks should only be disabled for requests that you expect to 234 | come from outside of your website. Do not disable CSRF checks for requests 235 | that you expect to only come from your website. An existing session, even if 236 | it belongs to an authenticated user, is not enough to protect against CSRF 237 | attacks. 238 | 239 | The following is an example of how to order your routes so that certain endpoints 240 | do not check for a valid CSRF token. 241 | 242 | ```js 243 | var cookieParser = require('cookie-parser') 244 | var csrf = require('csurf') 245 | var bodyParser = require('body-parser') 246 | var express = require('express') 247 | 248 | // create express app 249 | var app = express() 250 | 251 | // create api router 252 | var api = createApiRouter() 253 | 254 | // mount api before csrf is appended to the app stack 255 | app.use('/api', api) 256 | 257 | // now add csrf and other middlewares, after the "/api" was mounted 258 | app.use(bodyParser.urlencoded({ extended: false })) 259 | app.use(cookieParser()) 260 | app.use(csrf({ cookie: true })) 261 | 262 | app.get('/form', function (req, res) { 263 | // pass the csrfToken to the view 264 | res.render('send', { csrfToken: req.csrfToken() }) 265 | }) 266 | 267 | app.post('/process', function (req, res) { 268 | res.send('csrf was required to get here') 269 | }) 270 | 271 | function createApiRouter () { 272 | var router = new express.Router() 273 | 274 | router.post('/getProfile', function (req, res) { 275 | res.send('no csrf to get here') 276 | }) 277 | 278 | return router 279 | } 280 | ``` 281 | 282 | ### Custom error handling 283 | 284 | When the CSRF token validation fails, an error is thrown that has 285 | `err.code === 'EBADCSRFTOKEN'`. This can be used to display custom 286 | error messages. 287 | 288 | ```js 289 | var bodyParser = require('body-parser') 290 | var cookieParser = require('cookie-parser') 291 | var csrf = require('csurf') 292 | var express = require('express') 293 | 294 | var app = express() 295 | app.use(bodyParser.urlencoded({ extended: false })) 296 | app.use(cookieParser()) 297 | app.use(csrf({ cookie: true })) 298 | 299 | // error handler 300 | app.use(function (err, req, res, next) { 301 | if (err.code !== 'EBADCSRFTOKEN') return next(err) 302 | 303 | // handle CSRF token errors here 304 | res.status(403) 305 | res.send('form tampered with') 306 | }) 307 | ``` 308 | 309 | ## References 310 | 311 | - [Cross-side request forgery on Wikipedia][wikipedia-csrf] 312 | - [OWASP Cross-Site Request Forgery Prevention Cheat Sheet][owsap-csrf] 313 | 314 | [owsap-csrf]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html 315 | [owsap-csrf-double-submit]: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie 316 | [wikipedia-csrf]: https://en.wikipedia.org/wiki/Cross-site_request_forgery 317 | 318 | ## License 319 | 320 | [MIT](LICENSE) 321 | 322 | [coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/csurf/master 323 | [coveralls-url]: https://coveralls.io/r/expressjs/csurf?branch=master 324 | [node-url]: https://nodejs.org/en/download 325 | [npm-downloads-image]: https://badgen.net/npm/dm/csurf 326 | [npm-url]: https://npmjs.org/package/csurf 327 | [npm-version-image]: https://badgen.net/npm/v/csurf 328 | [github-actions-ci-image]: https://badgen.net/github/checks/expressjs/csurf/master?label=ci 329 | [github-actions-ci-url]: https://github.com/expressjs/csurf/actions/workflows/ci.yml 330 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csurf", 3 | "description": "CSRF token middleware", 4 | "version": "1.11.0", 5 | "author": "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)", 6 | "contributors": [ 7 | "Douglas Christopher Wilson <doug@somethingdoug.com>" 8 | ], 9 | "license": "MIT", 10 | "repository": "expressjs/csurf", 11 | "keywords": [ 12 | "csrf", 13 | "tokens", 14 | "middleware", 15 | "express" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------