Skip to content

Commit e3c0ef5

Browse files
[LiveComponent] Remove CSRF tokens - rely on same-origin/CORS instead
1 parent d27bb10 commit e3c0ef5

39 files changed

+107
-414
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 2.22.0
4+
5+
- Remove CSRF tokens - rely on same-origin/CORS instead
6+
7+
## 2.19.0
8+
39
- Add `submitForm()` to `TestLiveComponent`.
410
- Add `live_action` Twig function
511

src/LiveComponent/assets/dist/Backend/Backend.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,19 @@ export interface BackendInterface {
1313
}, files: {
1414
[key: string]: FileList;
1515
}): BackendRequest;
16-
updateCsrfToken(csrfToken: string): void;
1716
}
1817
export interface BackendAction {
1918
name: string;
2019
args: Record<string, string>;
2120
}
2221
export default class implements BackendInterface {
2322
private readonly requestBuilder;
24-
constructor(url: string, method?: 'get' | 'post', csrfToken?: string | null);
23+
constructor(url: string, method?: 'get' | 'post');
2524
makeRequest(props: any, actions: BackendAction[], updated: {
2625
[key: string]: any;
2726
}, children: ChildrenFingerprints, updatedPropsFromParent: {
2827
[key: string]: any;
2928
}, files: {
3029
[key: string]: FileList;
3130
}): BackendRequest;
32-
updateCsrfToken(csrfToken: string): void;
3331
}

src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
22
export default class {
33
private url;
44
private method;
5-
private csrfToken;
6-
constructor(url: string, method?: 'get' | 'post', csrfToken?: string | null);
5+
constructor(url: string, method?: 'get' | 'post');
76
buildRequest(props: any, actions: BackendAction[], updated: {
87
[key: string]: any;
98
}, children: ChildrenFingerprints, updatedPropsFromParent: {
@@ -15,5 +14,4 @@ export default class {
1514
fetchOptions: RequestInit;
1615
};
1716
private willDataFitInUrl;
18-
updateCsrfToken(csrfToken: string): void;
1917
}

src/LiveComponent/assets/dist/live_controller.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
2525
type: ObjectConstructor;
2626
default: {};
2727
};
28-
csrf: StringConstructor;
2928
listeners: {
3029
type: ArrayConstructor;
3130
default: never[];
@@ -59,7 +58,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
5958
readonly urlValue: string;
6059
readonly propsValue: any;
6160
propsUpdatedFromParentValue: any;
62-
readonly csrfValue: string;
6361
readonly listenersValue: Array<{
6462
event: string;
6563
action: string;

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,9 +2052,6 @@ class Component {
20522052
return response;
20532053
}
20542054
this.processRerender(html, backendResponse);
2055-
if (this.element.dataset.liveCsrfValue) {
2056-
this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
2057-
}
20582055
this.backendRequest = null;
20592056
thisPromiseResolve(backendResponse);
20602057
if (this.isRequestPending) {
@@ -2255,10 +2252,9 @@ class BackendRequest {
22552252
}
22562253

22572254
class RequestBuilder {
2258-
constructor(url, method = 'post', csrfToken = null) {
2255+
constructor(url, method = 'post') {
22592256
this.url = url;
22602257
this.method = method;
2261-
this.csrfToken = csrfToken;
22622258
}
22632259
buildRequest(props, actions, updated, children, updatedPropsFromParent, files) {
22642260
const splitUrl = this.url.split('?');
@@ -2295,9 +2291,6 @@ class RequestBuilder {
22952291
if (hasFingerprints) {
22962292
requestData.children = children;
22972293
}
2298-
if (this.csrfToken && (actions.length || totalFiles)) {
2299-
fetchOptions.headers['X-CSRF-TOKEN'] = this.csrfToken;
2300-
}
23012294
if (actions.length > 0) {
23022295
if (actions.length === 1) {
23032296
requestData.args = actions[0].args;
@@ -2328,22 +2321,16 @@ class RequestBuilder {
23282321
const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
23292322
return (urlEncodedJsonData + params.toString()).length < 1500;
23302323
}
2331-
updateCsrfToken(csrfToken) {
2332-
this.csrfToken = csrfToken;
2333-
}
23342324
}
23352325

23362326
class Backend {
2337-
constructor(url, method = 'post', csrfToken = null) {
2338-
this.requestBuilder = new RequestBuilder(url, method, csrfToken);
2327+
constructor(url, method = 'post') {
2328+
this.requestBuilder = new RequestBuilder(url, method);
23392329
}
23402330
makeRequest(props, actions, updated, children, updatedPropsFromParent, files) {
23412331
const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
23422332
return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
23432333
}
2344-
updateCsrfToken(csrfToken) {
2345-
this.requestBuilder.updateCsrfToken(csrfToken);
2346-
}
23472334
}
23482335

23492336
class StimulusElementDriver {
@@ -3203,7 +3190,6 @@ LiveControllerDefault.values = {
32033190
url: String,
32043191
props: { type: Object, default: {} },
32053192
propsUpdatedFromParent: { type: Object, default: {} },
3206-
csrf: String,
32073193
listeners: { type: Array, default: [] },
32083194
eventsToEmit: { type: Array, default: [] },
32093195
eventsToDispatch: { type: Array, default: [] },
@@ -3212,6 +3198,6 @@ LiveControllerDefault.values = {
32123198
requestMethod: { type: String, default: 'post' },
32133199
queryMapping: { type: Object, default: {} },
32143200
};
3215-
LiveControllerDefault.backendFactory = (controller) => new Backend(controller.urlValue, controller.requestMethodValue, controller.csrfValue);
3201+
LiveControllerDefault.backendFactory = (controller) => new Backend(controller.urlValue, controller.requestMethodValue);
32163202

32173203
export { Component, LiveControllerDefault as default, getComponent };

src/LiveComponent/assets/src/Backend/Backend.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export interface BackendInterface {
1515
updatedPropsFromParent: { [key: string]: any },
1616
files: { [key: string]: FileList }
1717
): BackendRequest;
18-
updateCsrfToken(csrfToken: string): void;
1918
}
2019

2120
export interface BackendAction {
@@ -26,8 +25,8 @@ export interface BackendAction {
2625
export default class implements BackendInterface {
2726
private readonly requestBuilder: RequestBuilder;
2827

29-
constructor(url: string, method: 'get' | 'post' = 'post', csrfToken: string | null = null) {
30-
this.requestBuilder = new RequestBuilder(url, method, csrfToken);
28+
constructor(url: string, method: 'get' | 'post' = 'post') {
29+
this.requestBuilder = new RequestBuilder(url, method);
3130
}
3231

3332
makeRequest(
@@ -53,8 +52,4 @@ export default class implements BackendInterface {
5352
Object.keys(updated)
5453
);
5554
}
56-
57-
updateCsrfToken(csrfToken: string) {
58-
this.requestBuilder.updateCsrfToken(csrfToken);
59-
}
6055
}

src/LiveComponent/assets/src/Backend/RequestBuilder.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
33
export default class {
44
private url: string;
55
private method: 'get' | 'post';
6-
private csrfToken: string | null;
76

8-
constructor(url: string, method: 'get' | 'post' = 'post', csrfToken: string | null = null) {
7+
constructor(url: string, method: 'get' | 'post' = 'post') {
98
this.url = url;
109
this.method = method;
11-
this.csrfToken = csrfToken;
1210
}
1311

1412
buildRequest(
@@ -64,10 +62,6 @@ export default class {
6462
requestData.children = children;
6563
}
6664

67-
if (this.csrfToken && (actions.length || totalFiles)) {
68-
fetchOptions.headers['X-CSRF-TOKEN'] = this.csrfToken;
69-
}
70-
7165
if (actions.length > 0) {
7266
// one or more ACTIONs
7367

@@ -117,8 +111,4 @@ export default class {
117111
// if the URL gets remotely close to 2000 chars, it may not fit
118112
return (urlEncodedJsonData + params.toString()).length < 1500;
119113
}
120-
121-
updateCsrfToken(csrfToken: string) {
122-
this.csrfToken = csrfToken;
123-
}
124114
}

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,6 @@ export default class Component {
329329

330330
this.processRerender(html, backendResponse);
331331

332-
// Store updated csrf token
333-
if (this.element.dataset.liveCsrfValue) {
334-
this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
335-
}
336-
337332
// finally resolve this promise
338333
this.backendRequest = null;
339334
thisPromiseResolve(backendResponse);

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
3636
url: String,
3737
props: { type: Object, default: {} },
3838
propsUpdatedFromParent: { type: Object, default: {} },
39-
csrf: String,
4039
listeners: { type: Array, default: [] },
4140
eventsToEmit: { type: Array, default: [] },
4241
eventsToDispatch: { type: Array, default: [] },
@@ -50,7 +49,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
5049
declare readonly urlValue: string;
5150
declare readonly propsValue: any;
5251
declare propsUpdatedFromParentValue: any;
53-
declare readonly csrfValue: string;
5452
declare readonly listenersValue: Array<{ event: string; action: string }>;
5553
declare readonly eventsToEmitValue: Array<{
5654
event: string;
@@ -79,7 +77,7 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
7977
private pendingFiles: { [key: string]: HTMLInputElement } = {};
8078

8179
static backendFactory: (controller: LiveControllerDefault) => BackendInterface = (controller) =>
82-
new Backend(controller.urlValue, controller.requestMethodValue, controller.csrfValue);
80+
new Backend(controller.urlValue, controller.requestMethodValue);
8381

8482
initialize() {
8583
this.mutationObserver = new MutationObserver(this.onMutations.bind(this));

src/LiveComponent/assets/test/Backend/RequestBuilder.test.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import RequestBuilder from '../../src/Backend/RequestBuilder';
22

33
describe('buildRequest', () => {
44
it('sets basic data on GET request', () => {
5-
const builder = new RequestBuilder('/_components?existing_param=1', 'get', '_the_csrf_token');
5+
const builder = new RequestBuilder('/_components?existing_param=1', 'get');
66
const { url, fetchOptions } = builder.buildRequest(
77
{ firstName: 'Ryan' },
88
[],
@@ -23,7 +23,7 @@ describe('buildRequest', () => {
2323
});
2424

2525
it('sets basic data on POST request', () => {
26-
const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
26+
const builder = new RequestBuilder('/_components', 'post');
2727
const { url, fetchOptions } = builder.buildRequest(
2828
{ firstName: 'Ryan' },
2929
[
@@ -42,7 +42,6 @@ describe('buildRequest', () => {
4242
expect(fetchOptions.method).toEqual('POST');
4343
expect(fetchOptions.headers).toEqual({
4444
Accept: 'application/vnd.live-component+html',
45-
'X-CSRF-TOKEN': '_the_csrf_token',
4645
'X-Requested-With': 'XMLHttpRequest',
4746
});
4847
const body = <FormData>fetchOptions.body;
@@ -58,7 +57,7 @@ describe('buildRequest', () => {
5857
});
5958

6059
it('sets basic data on POST request with batch actions', () => {
61-
const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
60+
const builder = new RequestBuilder('/_components', 'post');
6261
const { url, fetchOptions } = builder.buildRequest(
6362
{ firstName: 'Ryan' },
6463
[
@@ -101,7 +100,7 @@ describe('buildRequest', () => {
101100

102101
// when data is too long it makes a post request
103102
it('makes a POST request when data is too long', () => {
104-
const builder = new RequestBuilder('/_components', 'get', '_the_csrf_token');
103+
const builder = new RequestBuilder('/_components', 'get');
105104
const { url, fetchOptions } = builder.buildRequest(
106105
{ firstName: 'Ryan'.repeat(1000) },
107106
[],
@@ -129,7 +128,7 @@ describe('buildRequest', () => {
129128
});
130129

131130
it('makes a POST request when method is post', () => {
132-
const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
131+
const builder = new RequestBuilder('/_components', 'post');
133132
const { url, fetchOptions } = builder.buildRequest(
134133
{
135134
firstName: 'Ryan',
@@ -161,7 +160,7 @@ describe('buildRequest', () => {
161160
});
162161

163162
it('sends propsFromParent when specified', () => {
164-
const builder = new RequestBuilder('/_components?existing_param=1', 'get', '_the_csrf_token');
163+
const builder = new RequestBuilder('/_components?existing_param=1', 'get');
165164
const { url } = builder.buildRequest({ firstName: 'Ryan' }, [], { firstName: 'Kevin' }, {}, { count: 5 }, {});
166165

167166
expect(url).toEqual(
@@ -216,7 +215,7 @@ describe('buildRequest', () => {
216215
};
217216

218217
it('Sends file with request', () => {
219-
const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
218+
const builder = new RequestBuilder('/_components', 'post');
220219

221220
const { url, fetchOptions } = builder.buildRequest(
222221
{ firstName: 'Ryan' },
@@ -231,7 +230,6 @@ describe('buildRequest', () => {
231230
expect(fetchOptions.method).toEqual('POST');
232231
expect(fetchOptions.headers).toEqual({
233232
Accept: 'application/vnd.live-component+html',
234-
'X-CSRF-TOKEN': '_the_csrf_token',
235233
'X-Requested-With': 'XMLHttpRequest',
236234
});
237235
const body = <FormData>fetchOptions.body;
@@ -241,7 +239,7 @@ describe('buildRequest', () => {
241239
});
242240

243241
it('Sends multiple files with request', () => {
244-
const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
242+
const builder = new RequestBuilder('/_components', 'post');
245243

246244
const { url, fetchOptions } = builder.buildRequest(
247245
{ firstName: 'Ryan' },
@@ -256,7 +254,6 @@ describe('buildRequest', () => {
256254
expect(fetchOptions.method).toEqual('POST');
257255
expect(fetchOptions.headers).toEqual({
258256
Accept: 'application/vnd.live-component+html',
259-
'X-CSRF-TOKEN': '_the_csrf_token',
260257
'X-Requested-With': 'XMLHttpRequest',
261258
});
262259
const body = <FormData>fetchOptions.body;

0 commit comments

Comments
 (0)