3 * Copyright(c) 2011 Sencha Inc.
4 * Copyright(c) 2014 Jonathan Ong
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
10 * Module dependencies.
14 var Cookie = require('cookie');
15 var createError = require('http-errors');
16 var sign = require('cookie-signature').sign;
17 var Tokens = require('csrf');
20 * CSRF protection middleware.
22 * This middleware adds a `req.csrfToken()` function to make a token
23 * which should be added to requests which mutate
24 * state, within a hidden form field, query-string etc. This
25 * token is validated against the visitor's session.
27 * @param {Object} options
28 * @return {Function} middleware
32 module.exports = function csurf(options) {
33 options = options || {};
36 var cookie = getCookieOptions(options.cookie)
38 // get session options
39 var sessionKey = options.sessionKey || 'session'
42 var value = options.value || defaultValue
45 var tokens = new Tokens(options);
48 var ignoreMethods = options.ignoreMethods === undefined
49 ? ['GET', 'HEAD', 'OPTIONS']
50 : options.ignoreMethods
52 if (!Array.isArray(ignoreMethods)) {
53 throw new TypeError('option ignoreMethods must be an array')
57 var ignoreMethod = getIgnoredMethods(ignoreMethods)
59 return function csrf(req, res, next) {
60 var secret = getsecret(req, sessionKey, cookie)
63 // lazy-load token getter
64 req.csrfToken = function csrfToken() {
66 ? getsecret(req, sessionKey, cookie)
69 // use cached token if secret has not changed
70 if (token && sec === secret) {
74 // generate & set new secret
75 if (sec === undefined) {
76 sec = tokens.secretSync()
77 setsecret(req, res, sessionKey, sec, cookie)
80 // update changed secret
84 token = tokens.create(secret)
89 // generate & set secret
91 secret = tokens.secretSync()
92 setsecret(req, res, sessionKey, secret, cookie)
95 // verify the incoming token
96 if (!ignoreMethod[req.method]) {
97 verifytoken(req, tokens, secret, value(req))
105 * Default value function, checking the `req.body`
106 * and `req.query` for the CSRF token.
108 * @param {IncomingMessage} req
113 function defaultValue(req) {
114 return (req.body && req.body._csrf)
115 || (req.query && req.query._csrf)
116 || (req.headers['csrf-token'])
117 || (req.headers['xsrf-token'])
118 || (req.headers['x-csrf-token'])
119 || (req.headers['x-xsrf-token']);
123 * Get options for cookie.
125 * @param {boolean|object} [options]
130 function getCookieOptions(options) {
131 if (options !== true && typeof options !== 'object') {
140 if (options && typeof options === 'object') {
141 for (var prop in options) {
142 var val = options[prop]
144 if (val !== undefined) {
154 * Get a lookup of ignored methods.
156 * @param {array} methods
161 function getIgnoredMethods(methods) {
162 var obj = Object.create(null)
164 for (var i = 0; i < methods.length; i++) {
165 var method = methods[i].toUpperCase()
173 * Get the token secret from the request.
175 * @param {IncomingMessage} req
176 * @param {String} sessionKey
177 * @param {Object} [cookie]
181 function getsecret(req, sessionKey, cookie) {
185 // get secret from cookie
186 var bag = cookie.signed
190 secret = req[bag][cookie.key]
191 } else if (req[sessionKey]) {
192 // get secret from session
193 secret = req[sessionKey].csrfSecret
195 throw new Error('misconfigured csrf')
202 * Set a cookie on the HTTP response.
204 * @param {OutgoingMessage} res
205 * @param {string} name
206 * @param {string} val
207 * @param {Object} [options]
211 function setcookie(res, name, val, options) {
212 var data = Cookie.serialize(name, val, options);
214 var prev = res.getHeader('set-cookie') || [];
215 var header = Array.isArray(prev) ? prev.concat(data)
216 : Array.isArray(data) ? [prev].concat(data)
219 res.setHeader('set-cookie', header);
223 * Set the token secret on the request.
225 * @param {IncomingMessage} req
226 * @param {OutgoingMessage} res
227 * @param {string} sessionKey
228 * @param {string} val
229 * @param {Object} [cookie]
233 function setsecret(req, res, sessionKey, val, cookie) {
235 // set secret on cookie
237 var secret = req.secret
240 throw new Error('cookieParser("secret") required for signed cookies')
243 val = 's:' + sign(val, secret)
246 setcookie(res, cookie.key, val, cookie);
247 } else if (req[sessionKey]) {
248 // set secret on session
249 req[sessionKey].csrfSecret = val
251 /* istanbul ignore next: should never actually run */
252 throw new Error('misconfigured csrf')
259 * @param {IncomingMessage} req
260 * @param {Object} tokens
261 * @param {string} secret
262 * @param {string} val
266 function verifytoken(req, tokens, secret, val) {
268 if (!tokens.verify(secret, val)) {
269 throw createError(403, 'invalid csrf token', {
270 code: 'EBADCSRFTOKEN'