Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔏 feat: add csrf protection #205

Merged
merged 13 commits into from Aug 30, 2017
Merged

🔏 feat: add csrf protection #205

merged 13 commits into from Aug 30, 2017

Conversation

adamkdean
Copy link
Contributor

This introduces CSRF protection.

Closes #59.

About

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. More about CSRF.

This feature allows developers to use a per-request CSRF token which will be injected into the view model, and ensures that all POST requests supply a correct CSRF token. Without a correct token, with CSRF enabled, users will be greeted with a 403.

This feature uses csurf.

Usage

To enable CSRF, set the security.csrf config option in your config/config.{env}.json file:

"security": {
  "csrf": true
}

Once enabled, the variable csrfToken will be injected into the viewModel. You will need to add this to any forms which perform a POST using the field name _csrf, like so:

<form action="/" method="post">
  <input type="text" name="test_input_safe">
  <input type="hidden" name="_csrf" value="{csrfToken}">
  <input type="submit" value="Submit form">
</form>

If the CSRF token provided is incorrect, or one isn't provided, then a 403 forbidden error will occur.

A working example can be found here: dadi-web-csrf-test.

@adamkdean
Copy link
Contributor Author

@jimlambie do you know why these tests failed? Am I a bad person or is it an unrelated issue?

@mingard
Copy link
Contributor

mingard commented Aug 22, 2017

@adamkdean something funny going on with the doc param in the config schema e.g.

  csrf: {
      doc:
        'If true, a CSRF token will be provided, and all form submissions must include this as _csrf',
      format: '*',
      default: false
    }

@adamkdean
Copy link
Contributor Author

@mingard oh wow, just seen this now in the diff, that's weird, looks like something has changed all the docs? I'll fix it tomorrow and tidy it up. Good spot!

@jimlambie
Copy link
Contributor

Actually, @adamkdean, I think the issue is due to an incorrect version of sinon being installed. Please change in the package file:

from "sinon": "latest",
to "sinon": "2.x.x",

@abovedave
Copy link
Contributor

@adamkdean no need to be alarmed, it's prettier (trimming the line length)

@adamkdean
Copy link
Contributor Author

@abovebored @jimlambie ready for review

Copy link
Contributor

@jimlambie jimlambie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good so far, but it'd be great to see some tests to show that it works as expected. We can write these for you, if we know what the expected behaviour is - does it added headers? What's the expected response if a token doesn't exist? What's the expected response if a token is invalid?

@adamkdean
Copy link
Contributor Author

adamkdean commented Aug 23, 2017

@jimlambie my understanding of csurf's implementation is that if it's used (csrf enabled in web config), then any POST requests must provide a valid token under the name _csrf, and if it either isn't present or is incorrect, then a 403 is generated, which currently gets picked up by web for the custom error page. If you could knock up a couple of tests for this, that'd be great!

Also, probably worth testing that if csrf is enabled, that the csrfToken is present on the view model.

@jimlambie
Copy link
Contributor

@adamkdean I have added 4 tests in test/unit/controller.js, however the final test (HTTP 200 with valid token) is failing. Could you please have a look, as perhaps I misunderstand the implementation.

Adding the following to node_modules/csrf/index.js L142 gives me different token values:

console.log(token, expected)

@adamkdean
Copy link
Contributor Author

FYI the issue was that the secret is stored in a cookie, and needs to be passed back to the host with the next request, though if sessions are enabled, they are used instead.

@adamkdean
Copy link
Contributor Author

This time we're gonna get ✅ , I can feel it.

@@ -17,6 +17,8 @@ var raven = require('raven')
var serveFavicon = require('serve-favicon')
var serveStatic = require('serve-static')
var session = require('express-session')
var csrf = require('csurf')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus points (and a beer) are granted if you replace var with const/let as you go. ⭐️ 🍺

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eduardoboucas I wasn't sure if we were doing that or if the plan was to stick with the existing style. Maybe it'd be worth tidying up the dependencies at least.

Copy link
Contributor

@eduardoboucas eduardoboucas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🦄

Copy link
Contributor

@abovedave abovedave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've enabled this by default and also made a change so the error stack (for this and all other errors) are hidden when the environment is set to production mode 🕵🏻

@eduardoboucas
Copy link
Contributor

We've enabled this by default and also made a change so the error stack (for this and all other errors) are hidden when the environment is set to production mode 🕵🏻

Does making this enabled by default introduce a breaking change?

@jimlambie
Copy link
Contributor

jimlambie commented Aug 30, 2017

An excellent point. Perhaps something we can wait to introduce in the next major release?

@eduardoboucas
Copy link
Contributor

If I understand correctly, if this is disabled in the config then everything works exactly as before, right? If that's the case, perhaps we can introduce the feature but make it disabled by default and revisit when we release the next major.

@jimlambie jimlambie merged commit 0dfc0b0 into master Aug 30, 2017
@jimlambie jimlambie deleted the feature/csrf branch August 30, 2017 10:02
@adamkdean
Copy link
Contributor Author

@eduardoboucas yes, you're correct. When disabled, everything will work as expected, but if enabled by default, all existing forms will break. Definitely requires a major bump.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants