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

Enable stateless headers/cookies-based CSRF protection #1337

Merged
merged 1 commit into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions symfony/framework-bundle/7.2/config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ framework:
#esi: true
#fragments: true

# Enable stateless CSRF protection for forms and logins/logouts
form: { csrf_protection: { token_id: submit } }
csrf_protection:
stateless_token_ids: [submit, authenticate, logout]
fabpot marked this conversation as resolved.
Show resolved Hide resolved

when@test:
framework:
test: true
Expand Down
2 changes: 2 additions & 0 deletions symfony/stimulus-bundle/2.20/assets/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);
4 changes: 4 additions & 0 deletions symfony/stimulus-bundle/2.20/assets/controllers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"controllers": [],
"entrypoints": []
}
nicolas-grekas marked this conversation as resolved.
Show resolved Hide resolved
nicolas-grekas marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
var nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
var tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;

// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
document.addEventListener('submit', function (event) {
var csrfField = event.target.querySelector('input[data-controller="csrf-protection"]');

if (!csrfField) {
return;
}

var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
var csrfToken = csrfField.value;

if (!csrfCookie && nameCheck.test(csrfToken)) {
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
csrfField.value = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
}

if (csrfCookie && tokenCheck.test(csrfToken)) {
var cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
});

// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]');

if (!csrfField) {
return;
}

var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
event.detail.formSubmission.fetchRequest.headers[csrfCookie] = csrfField.value;
}
});

// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]');

if (!csrfField) {
return;
}

var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
var cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';

document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
}
});

/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Controller } from '@hotwired/stimulus';

/*
* This is an example Stimulus controller!
*
* Any element with a data-controller="hello" attribute will cause
* this controller to be executed. The name "hello" comes from the filename:
* hello_controller.js -> "hello"
*
* Delete this file or adapt it for your use!
*/
export default class extends Controller {
connect() {
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
}
}
41 changes: 41 additions & 0 deletions symfony/stimulus-bundle/2.20/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"bundles": {
"Symfony\\UX\\StimulusBundle\\StimulusBundle": ["all"]
},
"copy-from-recipe": {
"assets/": "assets/"
},
"aliases": ["stimulus", "stimulus-bundle"],
"conflict": {
"symfony/framework-bundle": "<7.2",
"symfony/security-csrf": "<7.2",
"symfony/webpack-encore-bundle": "<2.0",
"symfony/flex": "<1.20.0 || >=2.0.0,<2.3.0"
},
"add-lines": [
{
"file": "webpack.config.js",
"content": "\n // enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)\n .enableStimulusBridge('./assets/controllers.json')",
"position": "after_target",
"target": ".splitEntryChunks()"
},
{
"file": "assets/app.js",
"content": "import './bootstrap.js';",
"position": "top",
"warn_if_missing": true
},
{
"file": "assets/bootstrap.js",
"content": "import { startStimulusApp } from '@symfony/stimulus-bridge';\n\n// Registers Stimulus controllers from controllers.json and in the controllers/ directory\nexport const app = startStimulusApp(require.context(\n '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',\n true,\n /\\.[jt]sx?$/\n));",
"position": "top",
"requires": "symfony/webpack-encore-bundle"
},
{
"file": "assets/bootstrap.js",
"content": "import { startStimulusApp } from '@symfony/stimulus-bundle';\n\nconst app = startStimulusApp();",
"position": "top",
"requires": "symfony/asset-mapper"
}
]
}
14 changes: 14 additions & 0 deletions symfony/ux-turbo/2.20/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"conflict": {
"symfony/framework-bundle": "<7.2",
"symfony/security-csrf": "<7.2"
},
"add-lines": [
{
"file": "config/packages/framework.yaml",
"position": "after_target",
"target": " csrf_protection:",
"content": " check_header: true"
}
]
}
Loading