Repository: symfony/ux
Pull request #2251 (2024-10-09 11:05:52)

[LiveComponent] Remove CSRF tokens - rely on same-origin/CORS instead
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | could be
| Issues        | Fixes https://github.com/symfony/ux/issues/2177
| License       | MIT

This PR replaces token-based CSRF-protection by same-origin/CORS policies.
It replaces #2250



onEXHovia: Many developers, for various reasons(anwser stackoverflow), can add `Access-Control-Allow-Origin: * ` header for all requests its loosens Single Origin Policy (SOP) and allows you to execute request using `XMLHttpRequest `  and setting `Accept` header. Classic way with hidden input, seems like more effective or at least an additional layer against CSRF.
nicolas-grekas: @onEXHovia thanks for the comment but I'm not sure what you're suggesting for this PR?
onEXHovia: > @onEXHovia thanks for the comment but I'm not sure what you're suggesting for this PR?

If we consider this implementation and current with hidden token, I see two different scenarios. Without documentation developers may not know that live component depends on SOP in js for protections CSRF and added header Access-Control-Allow-Origin could mean potential CSRF.

On the other hand, implementation with token is more explicit. Developer disable it through the component configuration and understands that could mean potential CSRF.

Discussion in #2250 
> Enabling CSRF by default means enabling the session, making requests stateful.

> CSRF protection is not a "feature", nobody is "using it" in the sense that it's a behavior that shouldn't have any visible effect to any normal workflow.

The same can be said about stateless requests, usually no one tries to make all requests stateless. I don't see any reason to remove the token based implementation, as a solution we could add a global configuration that configures the CSRF behavior for all components.
daFish: This MR helps with #2177 and fixes the issue. My attempt to fix it in #2254 is no longer needed.
smnandre: LGTM :) 

@nicolas-grekas can you update the CHANGELOG in the src/LiveComponent repository ?  



Patch:
diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md
index 112a1f9e..5674d75e 100644
--- a/src/LiveComponent/CHANGELOG.md
+++ b/src/LiveComponent/CHANGELOG.md
@@ -1,5 +1,11 @@
 # CHANGELOG
 
+## 2.22.0
+
+-   Remove CSRF tokens - rely on same-origin/CORS instead
+
+## 2.19.0
+
 -   Add `submitForm()` to `TestLiveComponent`.
 -   Add `live_action` Twig function
 
diff --git a/src/LiveComponent/assets/dist/Backend/Backend.d.ts b/src/LiveComponent/assets/dist/Backend/Backend.d.ts
index c664be92..bf934808 100644
--- a/src/LiveComponent/assets/dist/Backend/Backend.d.ts
+++ b/src/LiveComponent/assets/dist/Backend/Backend.d.ts
@@ -13,7 +13,6 @@ export interface BackendInterface {
     }, files: {
         [key: string]: FileList;
     }): BackendRequest;
-    updateCsrfToken(csrfToken: string): void;
 }
 export interface BackendAction {
     name: string;
@@ -21,7 +20,7 @@ export interface BackendAction {
 }
 export default class implements BackendInterface {
     private readonly requestBuilder;
-    constructor(url: string, method?: 'get' | 'post', csrfToken?: string | null);
+    constructor(url: string, method?: 'get' | 'post');
     makeRequest(props: any, actions: BackendAction[], updated: {
         [key: string]: any;
     }, children: ChildrenFingerprints, updatedPropsFromParent: {
@@ -29,5 +28,4 @@ export default class implements BackendInterface {
     }, files: {
         [key: string]: FileList;
     }): BackendRequest;
-    updateCsrfToken(csrfToken: string): void;
 }
diff --git a/src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts b/src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts
index 8a29af3b..45821c56 100644
--- a/src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts
+++ b/src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts
@@ -2,8 +2,7 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
 export default class {
     private url;
     private method;
-    private csrfToken;
-    constructor(url: string, method?: 'get' | 'post', csrfToken?: string | null);
+    constructor(url: string, method?: 'get' | 'post');
     buildRequest(props: any, actions: BackendAction[], updated: {
         [key: string]: any;
     }, children: ChildrenFingerprints, updatedPropsFromParent: {
@@ -15,5 +14,4 @@ export default class {
         fetchOptions: RequestInit;
     };
     private willDataFitInUrl;
-    updateCsrfToken(csrfToken: string): void;
 }
diff --git a/src/LiveComponent/assets/dist/live_controller.d.ts b/src/LiveComponent/assets/dist/live_controller.d.ts
index 9b365643..b25a5d06 100644
--- a/src/LiveComponent/assets/dist/live_controller.d.ts
+++ b/src/LiveComponent/assets/dist/live_controller.d.ts
@@ -25,7 +25,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
             type: ObjectConstructor;
             default: {};
         };
-        csrf: StringConstructor;
         listeners: {
             type: ArrayConstructor;
             default: never[];
@@ -59,7 +58,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
     readonly urlValue: string;
     readonly propsValue: any;
     propsUpdatedFromParentValue: any;
-    readonly csrfValue: string;
     readonly listenersValue: Array<{
         event: string;
         action: string;
diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js
index 22b6b68f..94c9f8dd 100644
--- a/src/LiveComponent/assets/dist/live_controller.js
+++ b/src/LiveComponent/assets/dist/live_controller.js
@@ -2052,9 +2052,6 @@ class Component {
                 return response;
             }
             this.processRerender(html, backendResponse);
-            if (this.element.dataset.liveCsrfValue) {
-                this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
-            }
             this.backendRequest = null;
             thisPromiseResolve(backendResponse);
             if (this.isRequestPending) {
@@ -2255,10 +2252,9 @@ class BackendRequest {
 }
 
 class RequestBuilder {
-    constructor(url, method = 'post', csrfToken = null) {
+    constructor(url, method = 'post') {
         this.url = url;
         this.method = method;
-        this.csrfToken = csrfToken;
     }
     buildRequest(props, actions, updated, children, updatedPropsFromParent, files) {
         const splitUrl = this.url.split('?');
@@ -2295,9 +2291,6 @@ class RequestBuilder {
             if (hasFingerprints) {
                 requestData.children = children;
             }
-            if (this.csrfToken && (actions.length || totalFiles)) {
-                fetchOptions.headers['X-CSRF-TOKEN'] = this.csrfToken;
-            }
             if (actions.length > 0) {
                 if (actions.length === 1) {
                     requestData.args = actions[0].args;
@@ -2328,22 +2321,16 @@ class RequestBuilder {
         const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
         return (urlEncodedJsonData + params.toString()).length < 1500;
     }
-    updateCsrfToken(csrfToken) {
-        this.csrfToken = csrfToken;
-    }
 }
 
 class Backend {
-    constructor(url, method = 'post', csrfToken = null) {
-        this.requestBuilder = new RequestBuilder(url, method, csrfToken);
+    constructor(url, method = 'post') {
+        this.requestBuilder = new RequestBuilder(url, method);
     }
     makeRequest(props, actions, updated, children, updatedPropsFromParent, files) {
         const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
         return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
     }
-    updateCsrfToken(csrfToken) {
-        this.requestBuilder.updateCsrfToken(csrfToken);
-    }
 }
 
 class StimulusElementDriver {
@@ -3203,7 +3190,6 @@ LiveControllerDefault.values = {
     url: String,
     props: { type: Object, default: {} },
     propsUpdatedFromParent: { type: Object, default: {} },
-    csrf: String,
     listeners: { type: Array, default: [] },
     eventsToEmit: { type: Array, default: [] },
     eventsToDispatch: { type: Array, default: [] },
@@ -3212,6 +3198,6 @@ LiveControllerDefault.values = {
     requestMethod: { type: String, default: 'post' },
     queryMapping: { type: Object, default: {} },
 };
-LiveControllerDefault.backendFactory = (controller) => new Backend(controller.urlValue, controller.requestMethodValue, controller.csrfValue);
+LiveControllerDefault.backendFactory = (controller) => new Backend(controller.urlValue, controller.requestMethodValue);
 
 export { Component, LiveControllerDefault as default, getComponent };
diff --git a/src/LiveComponent/assets/src/Backend/Backend.ts b/src/LiveComponent/assets/src/Backend/Backend.ts
index b3d019a4..1f456906 100644
--- a/src/LiveComponent/assets/src/Backend/Backend.ts
+++ b/src/LiveComponent/assets/src/Backend/Backend.ts
@@ -15,7 +15,6 @@ export interface BackendInterface {
         updatedPropsFromParent: { [key: string]: any },
         files: { [key: string]: FileList }
     ): BackendRequest;
-    updateCsrfToken(csrfToken: string): void;
 }
 
 export interface BackendAction {
@@ -26,8 +25,8 @@ export interface BackendAction {
 export default class implements BackendInterface {
     private readonly requestBuilder: RequestBuilder;
 
-    constructor(url: string, method: 'get' | 'post' = 'post', csrfToken: string | null = null) {
-        this.requestBuilder = new RequestBuilder(url, method, csrfToken);
+    constructor(url: string, method: 'get' | 'post' = 'post') {
+        this.requestBuilder = new RequestBuilder(url, method);
     }
 
     makeRequest(
@@ -53,8 +52,4 @@ export default class implements BackendInterface {
             Object.keys(updated)
         );
     }
-
-    updateCsrfToken(csrfToken: string) {
-        this.requestBuilder.updateCsrfToken(csrfToken);
-    }
 }
diff --git a/src/LiveComponent/assets/src/Backend/RequestBuilder.ts b/src/LiveComponent/assets/src/Backend/RequestBuilder.ts
index b9bd2fc1..533e34fe 100644
--- a/src/LiveComponent/assets/src/Backend/RequestBuilder.ts
+++ b/src/LiveComponent/assets/src/Backend/RequestBuilder.ts
@@ -3,12 +3,10 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
 export default class {
     private url: string;
     private method: 'get' | 'post';
-    private csrfToken: string | null;
 
-    constructor(url: string, method: 'get' | 'post' = 'post', csrfToken: string | null = null) {
+    constructor(url: string, method: 'get' | 'post' = 'post') {
         this.url = url;
         this.method = method;
-        this.csrfToken = csrfToken;
     }
 
     buildRequest(
@@ -64,10 +62,6 @@ export default class {
                 requestData.children = children;
             }
 
-            if (this.csrfToken && (actions.length || totalFiles)) {
-                fetchOptions.headers['X-CSRF-TOKEN'] = this.csrfToken;
-            }
-
             if (actions.length > 0) {
                 // one or more ACTIONs
 
@@ -117,8 +111,4 @@ export default class {
         // if the URL gets remotely close to 2000 chars, it may not fit
         return (urlEncodedJsonData + params.toString()).length < 1500;
     }
-
-    updateCsrfToken(csrfToken: string) {
-        this.csrfToken = csrfToken;
-    }
 }
diff --git a/src/LiveComponent/assets/src/Component/index.ts b/src/LiveComponent/assets/src/Component/index.ts
index 18c6a5de..28807c57 100644
--- a/src/LiveComponent/assets/src/Component/index.ts
+++ b/src/LiveComponent/assets/src/Component/index.ts
@@ -329,11 +329,6 @@ export default class Component {
 
             this.processRerender(html, backendResponse);
 
-            // Store updated csrf token
-            if (this.element.dataset.liveCsrfValue) {
-                this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
-            }
-
             // finally resolve this promise
             this.backendRequest = null;
             thisPromiseResolve(backendResponse);
diff --git a/src/LiveComponent/assets/src/live_controller.ts b/src/LiveComponent/assets/src/live_controller.ts
index aca5b195..3902d56f 100644
--- a/src/LiveComponent/assets/src/live_controller.ts
+++ b/src/LiveComponent/assets/src/live_controller.ts
@@ -36,7 +36,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
         url: String,
         props: { type: Object, default: {} },
         propsUpdatedFromParent: { type: Object, default: {} },
-        csrf: String,
         listeners: { type: Array, default: [] },
         eventsToEmit: { type: Array, default: [] },
         eventsToDispatch: { type: Array, default: [] },
@@ -50,7 +49,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
     declare readonly urlValue: string;
     declare readonly propsValue: any;
     declare propsUpdatedFromParentValue: any;
-    declare readonly csrfValue: string;
     declare readonly listenersValue: Array<{ event: string; action: string }>;
     declare readonly eventsToEmitValue: Array<{
         event: string;
@@ -79,7 +77,7 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
     private pendingFiles: { [key: string]: HTMLInputElement } = {};
 
     static backendFactory: (controller: LiveControllerDefault) => BackendInterface = (controller) =>
-        new Backend(controller.urlValue, controller.requestMethodValue, controller.csrfValue);
+        new Backend(controller.urlValue, controller.requestMethodValue);
 
     initialize() {
         this.mutationObserver = new MutationObserver(this.onMutations.bind(this));
diff --git a/src/LiveComponent/composer.json b/src/LiveComponent/composer.json
index 4647b494..7359a8b6 100644
--- a/src/LiveComponent/composer.json
+++ b/src/LiveComponent/composer.json
@@ -27,6 +27,7 @@
     },
     "require": {
         "php": ">=8.1",
+        "symfony/deprecation-contracts": "^2.5|^3.0",
         "symfony/property-access": "^5.4.5|^6.0|^7.0",
         "symfony/stimulus-bundle": "^2.9",
         "symfony/ux-twig-component": "^2.8",
diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst
index 02bf4465..64143096 100644
--- a/src/LiveComponent/doc/index.rst
+++ b/src/LiveComponent/doc/index.rst
@@ -1200,28 +1200,14 @@ Actions and CSRF Protection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 When you trigger an action, a POST request is sent that contains a
-``X-CSRF-TOKEN`` header. This header is automatically populated and
-validated. In other words… you get CSRF protection without any work.
+custom ``Accept`` header. This header is automatically populated and
+validated. In other words… you get CSRF protection without any work
+thanks to same-origin / CORS policies implemented by browsers.
 
-Your only job is to make sure that the CSRF component is installed:
+If you want this built-in CSRF protection to be effective, mind your
+CORS headers (e.g. *DO NOT* use `Access-Control-Allow-Origin: *`).
 
-.. code-block:: terminal
-
-    $ composer require symfony/security-csrf
-
-If you want to disable CSRF for a single component you can set
-``csrf`` option to ``false``::
-
-    namespace App\Twig\Components;
-
-    use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
-    use Symfony\UX\LiveComponent\Attribute\LiveProp;
-
-    #[AsLiveComponent(csrf: false)]
-    class MyLiveComponent
-    {
-        // ...
-    }
+(In test-mode, the CSRF protection is disabled to make testing easier.)
 
 Actions, Redirecting and AbstractController
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/LiveComponent/src/Attribute/AsLiveComponent.php b/src/LiveComponent/src/Attribute/AsLiveComponent.php
index 5f03e0b9..497eed31 100644
--- a/src/LiveComponent/src/Attribute/AsLiveComponent.php
+++ b/src/LiveComponent/src/Attribute/AsLiveComponent.php
@@ -24,33 +24,62 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
 #[\Attribute(\Attribute::TARGET_CLASS)]
 final class AsLiveComponent extends AsTwigComponent
 {
+    public string $route;
+    public string $method;
+    public int $urlReferenceType;
+
+    private ?string $defaultAction;
+
     /**
-     * @param string|null $name              The component name (ie: TodoList)
-     * @param string|null $template          The template path of the component (ie: components/TodoList.html.twig).
-     * @param string|null $defaultAction     The default action to call when the component is mounted (ie: __invoke)
-     * @param bool        $exposePublicProps Whether to expose every public property as a Twig variable
-     * @param string      $attributesVar     The name of the special "attributes" variable in the template
-     * @param bool        $csrf              Whether to enable CSRF protection (default: true)
-     * @param string      $route             The route used to render the component & handle actions (default: ux_live_component)
-     * @param int         $urlReferenceType  Which type of URL should be generated for the given route. Use the constants from UrlGeneratorInterface (default: absolute path, e.g. "/dir/file").
+     * @param string|null              $name              The component name (ie: TodoList)
+     * @param string|null              $template          The template path of the component (ie: components/TodoList.html.twig).
+     * @param string|null              $defaultAction     The default action to call when the component is mounted (ie: __invoke)
+     * @param bool                     $exposePublicProps Whether to expose every public property as a Twig variable
+     * @param string                   $attributesVar     The name of the special "attributes" variable in the template
+     * @param string                   $route             The route used to render the component & handle actions
+     * @param string                   $method            The HTTP method to use
+     * @param UrlGeneratorInterface::* $urlReferenceType  Which type of URL should be generated for the given route
      */
     public function __construct(
         ?string $name = null,
         ?string $template = null,
-        private ?string $defaultAction = null,
+        ?string $defaultAction = null,
         bool $exposePublicProps = true,
         string $attributesVar = 'attributes',
-        public bool $csrf = true,
-        public string $route = 'ux_live_component',
-        public string $method = 'post',
-        public int $urlReferenceType = UrlGeneratorInterface::ABSOLUTE_PATH,
+        string|bool $route = 'ux_live_component',
+        string $method = 'post',
+        int|string $urlReferenceType = UrlGeneratorInterface::ABSOLUTE_PATH,
+        public bool|int $csrf = true, // @deprecated
     ) {
+        if (8 < \func_num_args() || \is_bool($route)) {
+            trigger_deprecation('symfony/ux-live-component', '2.21', 'Argument "$csrf" of "#[%s]" has no effect anymore and is deprecated.', static::class);
+        }
+        if (\is_bool($route)) {
+            $this->csrf = $route;
+            $route = $method;
+            $method = $urlReferenceType;
+            $urlReferenceType = $csrf;
+
+            switch (\func_num_args()) {
+                case 6: $route = 'ux_live_component';
+                    // no break
+                case 7: $method = 'post';
+                    // no break
+                case 8: $urlReferenceType = UrlGeneratorInterface::ABSOLUTE_PATH;
+                    // no break
+                default:
+            }
+        }
+
         parent::__construct($name, $template, $exposePublicProps, $attributesVar);
 
-        $this->method = strtolower($this->method);
+        $this->defaultAction = $defaultAction;
+        $this->route = $route;
+        $this->method = strtolower($method);
+        $this->urlReferenceType = $urlReferenceType;
 
-        if (!\in_array($this->method, ['get', 'post'])) {
-            throw new \UnexpectedValueException('$method must be either \'get\' or \'post\'');
+        if (!\in_array($method, ['get', 'post'])) {
+            throw new \UnexpectedValueException('$method must be either \'get\' or \'post\'.');
         }
     }
 
diff --git a/src/LiveComponent/src/DependencyInjection/Compiler/OptionalDependencyPass.php b/src/LiveComponent/src/DependencyInjection/Compiler/OptionalDependencyPass.php
index 95180356..14bcc18c 100644
--- a/src/LiveComponent/src/DependencyInjection/Compiler/OptionalDependencyPass.php
+++ b/src/LiveComponent/src/DependencyInjection/Compiler/OptionalDependencyPass.php
@@ -33,5 +33,10 @@ final class OptionalDependencyPass implements CompilerPassInterface
                 ->addTag(LiveComponentBundle::HYDRATION_EXTENSION_TAG)
             ;
         }
+
+        if (!$container->hasDefinition('test.client')) {
+            $container->getDefinition('ux.live_component.event_subscriber')
+                ->setArgument(1, false);
+        }
     }
 }
diff --git a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
index c45b8409..28979e13 100644
--- a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
+++ b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
@@ -127,7 +127,6 @@ final class LiveComponentExtension extends Extension implements PrependExtension
             ->addTag('container.service_subscriber', ['key' => ComponentRenderer::class, 'id' => 'ux.twig_component.component_renderer'])
             ->addTag('container.service_subscriber', ['key' => LiveComponentHydrator::class, 'id' => 'ux.live_component.component_hydrator'])
             ->addTag('container.service_subscriber', ['key' => LiveComponentMetadataFactory::class, 'id' => 'ux.live_component.metadata_factory'])
-            ->addTag('container.service_subscriber') // csrf
         ;
 
         $container->register('ux.live_component.live_responder', LiveResponder::class);
@@ -204,7 +203,6 @@ final class LiveComponentExtension extends Extension implements PrependExtension
                 new Reference('ux.live_component.fingerprint_calculator'),
                 new Reference('router'),
                 new Reference('ux.live_component.live_responder'),
-                new Reference('security.csrf.token_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
                 new Reference('ux.live_component.twig.template_mapper'),
             ])
         ;
diff --git a/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php b/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
index 5d2480f4..58b5df6d 100644
--- a/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
+++ b/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
@@ -21,18 +21,14 @@ use Symfony\Component\HttpKernel\Event\ExceptionEvent;
 use Symfony\Component\HttpKernel\Event\RequestEvent;
 use Symfony\Component\HttpKernel\Event\ResponseEvent;
 use Symfony\Component\HttpKernel\Event\ViewEvent;
-use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
-use Symfony\Component\Security\Csrf\CsrfToken;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
 use Symfony\Contracts\Service\ServiceSubscriberInterface;
 use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
 use Symfony\UX\LiveComponent\Attribute\LiveArg;
 use Symfony\UX\LiveComponent\LiveComponentHydrator;
 use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
-use Symfony\UX\LiveComponent\Util\LiveControllerAttributesCreator;
 use Symfony\UX\TwigComponent\ComponentFactory;
 use Symfony\UX\TwigComponent\ComponentMetadata;
 use Symfony\UX\TwigComponent\ComponentRenderer;
@@ -49,8 +45,10 @@ class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscr
     private const HTML_CONTENT_TYPE = 'application/vnd.live-component+html';
     private const REDIRECT_HEADER = 'X-Live-Redirect';
 
-    public function __construct(private ContainerInterface $container)
-    {
+    public function __construct(
+        private ContainerInterface $container,
+        private bool $testMode = true,
+    ) {
     }
 
     public static function getSubscribedServices(): array
@@ -60,7 +58,6 @@ class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscr
             ComponentFactory::class,
             LiveComponentHydrator::class,
             LiveComponentMetadataFactory::class,
-            '?'.CsrfTokenManagerInterface::class,
         ];
     }
 
@@ -107,13 +104,6 @@ class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscr
             throw new MethodNotAllowedHttpException(['POST']);
         }
 
-        if (
-            $this->container->has(CsrfTokenManagerInterface::class)
-            && $metadata->get('csrf')
-            && !$this->container->get(CsrfTokenManagerInterface::class)->isTokenValid(new CsrfToken(LiveControllerAttributesCreator::getCsrfTokeName($componentName), $request->headers->get('X-CSRF-TOKEN')))) {
-            throw new BadRequestHttpException('Invalid CSRF token.');
-        }
-
         if ('_batch' === $action) {
             // use batch controller
             $data = $this->parseDataFor($request);
@@ -299,11 +289,12 @@ class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscr
             return;
         }
 
-        if (!\in_array(self::HTML_CONTENT_TYPE, $request->getAcceptableContentTypes(), true)) {
+        if (!$response->isRedirection()) {
             return;
         }
 
-        if (!$response->isRedirection()) {
+        if ($this->testMode && !\in_array(self::HTML_CONTENT_TYPE, $request->getAcceptableContentTypes(), true)) {
+            // Make testing redirections easier
             return;
         }
 
@@ -344,7 +335,17 @@ class LiveComponentSubscriber implements EventSubscriberInterface, ServiceSubscr
 
     private function isLiveComponentRequest(Request $request): bool
     {
-        return $request->attributes->has('_live_component');
+        if (!$request->attributes->has('_live_component')) {
+            return false;
+        }
+
+        if ($this->testMode) {
+            return true;
+        }
+
+        // Except when testing, require the correct content-type in the Accept header.
+        // This also acts as a CSRF protection since this can only be set in accordance with same-origin/CORS policies.
+        return \in_array(self::HTML_CONTENT_TYPE, $request->getAcceptableContentTypes(), true);
     }
 
     private function hydrateComponent(object $component, string $componentName, Request $request): MountedComponent
diff --git a/src/LiveComponent/src/Util/ChildComponentPartialRenderer.php b/src/LiveComponent/src/Util/ChildComponentPartialRenderer.php
index 7920747b..79e9c6eb 100644
--- a/src/LiveComponent/src/Util/ChildComponentPartialRenderer.php
+++ b/src/LiveComponent/src/Util/ChildComponentPartialRenderer.php
@@ -72,7 +72,6 @@ class ChildComponentPartialRenderer implements ServiceSubscriberInterface
         // optional, but these just aren't needed by the frontend at this point
         unset($attributes['data-controller']);
         unset($attributes['data-live-url-value']);
-        unset($attributes['data-live-csrf-value']);
         unset($attributes['data-live-props-value']);
 
         return $this->createHtml($attributes, $childTag);
diff --git a/src/LiveComponent/src/Util/LiveAttributesCollection.php b/src/LiveComponent/src/Util/LiveAttributesCollection.php
index 64d6e734..71449419 100644
--- a/src/LiveComponent/src/Util/LiveAttributesCollection.php
+++ b/src/LiveComponent/src/Util/LiveAttributesCollection.php
@@ -83,11 +83,6 @@ final class LiveAttributesCollection
         $this->attributes['data-live-url-value'] = $url;
     }
 
-    public function setCsrf(string $csrf): void
-    {
-        $this->attributes['data-live-csrf-value'] = $csrf;
-    }
-
     public function setListeners(array $listeners): void
     {
         $this->attributes['data-live-listeners-value'] = $listeners;
diff --git a/src/LiveComponent/src/Util/LiveControllerAttributesCreator.php b/src/LiveComponent/src/Util/LiveControllerAttributesCreator.php
index cb1d8167..64d7866e 100644
--- a/src/LiveComponent/src/Util/LiveControllerAttributesCreator.php
+++ b/src/LiveComponent/src/Util/LiveControllerAttributesCreator.php
@@ -12,7 +12,6 @@
 namespace Symfony\UX\LiveComponent\Util;
 
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
-use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
 use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
 use Symfony\UX\LiveComponent\LiveComponentHydrator;
 use Symfony\UX\LiveComponent\LiveResponder;
@@ -45,7 +44,6 @@ class LiveControllerAttributesCreator
         private FingerprintCalculator $fingerprintCalculator,
         private UrlGeneratorInterface $urlGenerator,
         private LiveResponder $liveResponder,
-        private ?CsrfTokenManagerInterface $csrfTokenManager,
         private TemplateMap $templateMap,
     ) {
     }
@@ -131,20 +129,9 @@ class LiveControllerAttributesCreator
         );
         $attributesCollection->setProps($dehydratedProps->getProps());
 
-        if ($this->csrfTokenManager && $metadata->get('csrf')) {
-            $attributesCollection->setCsrf(
-                $this->csrfTokenManager->getToken(self::getCsrfTokeName($mounted->getName()))->getValue(),
-            );
-        }
-
         return $attributesCollection;
     }
 
-    public static function getCsrfTokeName(string $componentName): string
-    {
-        return 'live_component_'.$componentName;
-    }
-
     private function dehydrateComponent(string $name, object $component, ComponentAttributes $attributes): DehydratedProps
     {
         $liveMetadata = $this->metadataFactory->getMetadata($name);


Test patch:
diff --git a/src/LiveComponent/assets/test/Backend/RequestBuilder.test.ts b/src/LiveComponent/assets/test/Backend/RequestBuilder.test.ts
index 22f0e049..44521271 100644
--- a/src/LiveComponent/assets/test/Backend/RequestBuilder.test.ts
+++ b/src/LiveComponent/assets/test/Backend/RequestBuilder.test.ts
@@ -2,7 +2,7 @@ import RequestBuilder from '../../src/Backend/RequestBuilder';
 
 describe('buildRequest', () => {
     it('sets basic data on GET request', () => {
-        const builder = new RequestBuilder('/_components?existing_param=1', 'get', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components?existing_param=1', 'get');
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan' },
             [],
@@ -23,7 +23,7 @@ describe('buildRequest', () => {
     });
 
     it('sets basic data on POST request', () => {
-        const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'post');
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan' },
             [
@@ -42,7 +42,6 @@ describe('buildRequest', () => {
         expect(fetchOptions.method).toEqual('POST');
         expect(fetchOptions.headers).toEqual({
             Accept: 'application/vnd.live-component+html',
-            'X-CSRF-TOKEN': '_the_csrf_token',
             'X-Requested-With': 'XMLHttpRequest',
         });
         const body = <FormData>fetchOptions.body;
@@ -58,7 +57,7 @@ describe('buildRequest', () => {
     });
 
     it('sets basic data on POST request with batch actions', () => {
-        const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'post');
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan' },
             [
@@ -101,7 +100,7 @@ describe('buildRequest', () => {
 
     // when data is too long it makes a post request
     it('makes a POST request when data is too long', () => {
-        const builder = new RequestBuilder('/_components', 'get', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'get');
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan'.repeat(1000) },
             [],
@@ -129,7 +128,7 @@ describe('buildRequest', () => {
     });
 
     it('makes a POST request when method is post', () => {
-        const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'post');
         const { url, fetchOptions } = builder.buildRequest(
             {
                 firstName: 'Ryan',
@@ -161,7 +160,7 @@ describe('buildRequest', () => {
     });
 
     it('sends propsFromParent when specified', () => {
-        const builder = new RequestBuilder('/_components?existing_param=1', 'get', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components?existing_param=1', 'get');
         const { url } = builder.buildRequest({ firstName: 'Ryan' }, [], { firstName: 'Kevin' }, {}, { count: 5 }, {});
 
         expect(url).toEqual(
@@ -216,7 +215,7 @@ describe('buildRequest', () => {
     };
 
     it('Sends file with request', () => {
-        const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'post');
 
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan' },
@@ -231,7 +230,6 @@ describe('buildRequest', () => {
         expect(fetchOptions.method).toEqual('POST');
         expect(fetchOptions.headers).toEqual({
             Accept: 'application/vnd.live-component+html',
-            'X-CSRF-TOKEN': '_the_csrf_token',
             'X-Requested-With': 'XMLHttpRequest',
         });
         const body = <FormData>fetchOptions.body;
@@ -241,7 +239,7 @@ describe('buildRequest', () => {
     });
 
     it('Sends multiple files with request', () => {
-        const builder = new RequestBuilder('/_components', 'post', '_the_csrf_token');
+        const builder = new RequestBuilder('/_components', 'post');
 
         const { url, fetchOptions } = builder.buildRequest(
             { firstName: 'Ryan' },
@@ -256,7 +254,6 @@ describe('buildRequest', () => {
         expect(fetchOptions.method).toEqual('POST');
         expect(fetchOptions.headers).toEqual({
             Accept: 'application/vnd.live-component+html',
-            'X-CSRF-TOKEN': '_the_csrf_token',
             'X-Requested-With': 'XMLHttpRequest',
         });
         const body = <FormData>fetchOptions.body;
diff --git a/src/LiveComponent/assets/test/controller/render.test.ts b/src/LiveComponent/assets/test/controller/render.test.ts
index 0e328a61..efa875c0 100644
--- a/src/LiveComponent/assets/test/controller/render.test.ts
+++ b/src/LiveComponent/assets/test/controller/render.test.ts
@@ -630,23 +630,4 @@ describe('LiveController rendering Tests', () => {
         // verify the selectedIndex of the select option 2 is 0
         expect(selectOption2.selectedIndex).toBe(0);
     });
-
-    it('backend will have a new csrf token', async () => {
-        const test = await createTest(
-            {},
-            (data: any) => `
-            <div ${initComponent(data)} data-live-csrf-value="${data.csrf}">
-            </div>
-        `
-        );
-
-        test.expectsAjaxCall().serverWillChangeProps((data: any) => {
-            // change csrf token
-            data.csrf = 'Hello';
-        });
-
-        await test.component.render();
-
-        expect(test.mockedBackend.csrfToken).toEqual('Hello');
-    });
 });
diff --git a/src/LiveComponent/assets/test/tools.ts b/src/LiveComponent/assets/test/tools.ts
index 958fdcc8..791d2fe2 100644
--- a/src/LiveComponent/assets/test/tools.ts
+++ b/src/LiveComponent/assets/test/tools.ts
@@ -98,8 +98,6 @@ class FunctionalTest {
 class MockedBackend implements BackendInterface {
     private expectedMockedAjaxCalls: Array<MockedAjaxCall> = [];
 
-    public csrfToken: string | null = null;
-
     addMockedAjaxCall(mock: MockedAjaxCall) {
         this.expectedMockedAjaxCalls.push(mock);
     }
@@ -141,10 +139,6 @@ class MockedBackend implements BackendInterface {
         return matchedMock.createBackendRequest();
     }
 
-    updateCsrfToken(csrfToken: string) {
-        this.csrfToken = csrfToken;
-    }
-
     getExpectedMockedAjaxCalls(): Array<MockedAjaxCall> {
         return this.expectedMockedAjaxCalls;
     }
@@ -469,7 +463,6 @@ export function initComponent(props: any = {}, controllerValues: any = {}) {
         data-live-url-value="http://localhost/components/_test_component_${Math.round(Math.random() * 1000)}"
         data-live-props-value="${dataToJsonAttribute(props)}"
         ${controllerValues.debounce ? `data-live-debounce-value="${controllerValues.debounce}"` : ''}
-        ${controllerValues.csrf ? `data-live-csrf-value="${controllerValues.csrf}"` : ''}
         ${controllerValues.id ? `id="${controllerValues.id}"` : ''}
         ${controllerValues.fingerprint ? `data-live-fingerprint-value="${controllerValues.fingerprint}"` : ''}
         ${controllerValues.listeners ? `data-live-listeners-value="${dataToJsonAttribute(controllerValues.listeners)}"` : ''}
diff --git a/src/LiveComponent/src/Test/TestLiveComponent.php b/src/LiveComponent/src/Test/TestLiveComponent.php
index 98def7aa..0e587a38 100644
--- a/src/LiveComponent/src/Test/TestLiveComponent.php
+++ b/src/LiveComponent/src/Test/TestLiveComponent.php
@@ -147,8 +147,6 @@ final class TestLiveComponent
 
     private function request(array $content = [], ?string $action = null, array $files = []): self
     {
-        $csrfToken = $this->csrfToken();
-
         $this->client()->request(
             'POST',
             $this->router->generate(
@@ -161,7 +159,6 @@ final class TestLiveComponent
             ),
             parameters: ['data' => json_encode(array_merge($content, ['props' => $this->props()]))],
             files: $files,
-            server: $csrfToken ? ['HTTP_X_CSRF_TOKEN' => $csrfToken] : [],
         );
 
         return $this;
@@ -178,17 +175,6 @@ final class TestLiveComponent
         return json_decode($node->attr('data-live-props-value'), true, flags: \JSON_THROW_ON_ERROR);
     }
 
-    private function csrfToken(): ?string
-    {
-        $crawler = $this->client()->getCrawler();
-
-        if (!\count($node = $crawler->filter('[data-live-csrf-value]'))) {
-            return null;
-        }
-
-        return $node->attr('data-live-csrf-value');
-    }
-
     private function client(): KernelBrowser
     {
         if ($this->performedInitialRequest) {
diff --git a/src/LiveComponent/tests/Fixtures/Component/ComponentWithEmit.php b/src/LiveComponent/tests/Fixtures/Component/ComponentWithEmit.php
index 0c765ce2..2b8325c8 100644
--- a/src/LiveComponent/tests/Fixtures/Component/ComponentWithEmit.php
+++ b/src/LiveComponent/tests/Fixtures/Component/ComponentWithEmit.php
@@ -18,7 +18,7 @@ use Symfony\UX\LiveComponent\ComponentToolsTrait;
 use Symfony\UX\LiveComponent\DefaultActionTrait;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
 
-#[AsLiveComponent('component_with_emit', csrf: false)]
+#[AsLiveComponent('component_with_emit')]
 final class ComponentWithEmit
 {
     use DefaultActionTrait;
diff --git a/src/LiveComponent/tests/Fixtures/Component/DisabledCsrf.php b/src/LiveComponent/tests/Fixtures/Component/DisabledCsrf.php
deleted file mode 100644
index d3c7098f..00000000
--- a/src/LiveComponent/tests/Fixtures/Component/DisabledCsrf.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
-
-use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
-use Symfony\UX\LiveComponent\Attribute\LiveAction;
-use Symfony\UX\LiveComponent\Attribute\LiveProp;
-
-/**
- * @author Tomas Norkūnas <norkunas.tom@gmail.com>
- */
-#[AsLiveComponent('disabled_csrf', defaultAction: 'defaultAction()', csrf: false)]
-final class DisabledCsrf
-{
-    #[LiveProp]
-    public int $count = 1;
-
-    #[LiveAction]
-    public function increase(): void
-    {
-        ++$this->count;
-    }
-
-    public function defaultAction(): void
-    {
-    }
-}
diff --git a/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php b/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php
index 7ca5576c..1686b9a9 100644
--- a/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php
+++ b/src/LiveComponent/tests/Fixtures/Component/FormWithCollectionTypeComponent.php
@@ -25,8 +25,6 @@ use Symfony\UX\LiveComponent\Tests\Fixtures\Form\BlogPostFormType;
 #[AsLiveComponent('form_with_collection_type')]
 class FormWithCollectionTypeComponent extends AbstractController
 {
-    public bool $enableCsrf = false;
-
     use ComponentWithFormTrait;
     use DefaultActionTrait;
 
@@ -41,9 +39,7 @@ class FormWithCollectionTypeComponent extends AbstractController
 
     protected function instantiateForm(): FormInterface
     {
-        return $this->createForm(BlogPostFormType::class, $this->post, [
-            'csrf_protection' => $this->enableCsrf,
-        ]);
+        return $this->createForm(BlogPostFormType::class, $this->post);
     }
 
     #[LiveAction]
diff --git a/src/LiveComponent/tests/Fixtures/Component/ValidatingComponent.php b/src/LiveComponent/tests/Fixtures/Component/ValidatingComponent.php
index bbf90549..899c2d15 100644
--- a/src/LiveComponent/tests/Fixtures/Component/ValidatingComponent.php
+++ b/src/LiveComponent/tests/Fixtures/Component/ValidatingComponent.php
@@ -10,7 +10,7 @@ use Symfony\UX\LiveComponent\Attribute\LiveProp;
 use Symfony\UX\LiveComponent\DefaultActionTrait;
 use Symfony\UX\LiveComponent\ValidatableComponentTrait;
 
-#[AsLiveComponent('validating_component', csrf: false)]
+#[AsLiveComponent('validating_component')]
 final class ValidatingComponent
 {
     use DefaultActionTrait;
diff --git a/src/LiveComponent/tests/Fixtures/Form/BlogPostFormLiveCollectionType.php b/src/LiveComponent/tests/Fixtures/Form/BlogPostFormLiveCollectionType.php
index f0404323..e7898ee6 100644
--- a/src/LiveComponent/tests/Fixtures/Form/BlogPostFormLiveCollectionType.php
+++ b/src/LiveComponent/tests/Fixtures/Form/BlogPostFormLiveCollectionType.php
@@ -37,7 +37,6 @@ class BlogPostFormLiveCollectionType extends AbstractType
     public function configureOptions(OptionsResolver $resolver)
     {
         $resolver->setDefaults([
-            'csrf_protection' => false,
             'data_class' => BlogPost::class,
         ]);
     }
diff --git a/src/LiveComponent/tests/Fixtures/Form/BlogPostFormType.php b/src/LiveComponent/tests/Fixtures/Form/BlogPostFormType.php
index b99f789b..584bcdbc 100644
--- a/src/LiveComponent/tests/Fixtures/Form/BlogPostFormType.php
+++ b/src/LiveComponent/tests/Fixtures/Form/BlogPostFormType.php
@@ -38,7 +38,6 @@ class BlogPostFormType extends AbstractType
     {
         $resolver->setDefaults([
             'data_class' => BlogPost::class,
-            'csrf_protection' => false,
         ]);
     }
 }
diff --git a/src/LiveComponent/tests/Fixtures/Form/CommentFormType.php b/src/LiveComponent/tests/Fixtures/Form/CommentFormType.php
index bca009e1..e7b5e5e5 100644
--- a/src/LiveComponent/tests/Fixtures/Form/CommentFormType.php
+++ b/src/LiveComponent/tests/Fixtures/Form/CommentFormType.php
@@ -29,7 +29,6 @@ class CommentFormType extends AbstractType
     public function configureOptions(OptionsResolver $resolver)
     {
         $resolver->setDefaults([
-            'csrf_protection' => false,
             'data_class' => Comment::class,
         ]);
     }
diff --git a/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php b/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
index 7362cab2..ea69b138 100644
--- a/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
+++ b/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
@@ -71,11 +71,4 @@ class FormWithManyDifferentFieldsType extends AbstractType
             ->add('complexType', ComplexFieldType::class)
         ;
     }
-
-    public function configureOptions(OptionsResolver $resolver)
-    {
-        $resolver->setDefaults([
-            'csrf_protection' => false,
-        ]);
-    }
 }
diff --git a/src/LiveComponent/tests/Fixtures/Kernel.php b/src/LiveComponent/tests/Fixtures/Kernel.php
index 4e6b412f..508dd242 100644
--- a/src/LiveComponent/tests/Fixtures/Kernel.php
+++ b/src/LiveComponent/tests/Fixtures/Kernel.php
@@ -91,6 +91,7 @@ final class Kernel extends BaseKernel
     protected function configureContainer(ContainerConfigurator $c): void
     {
         $frameworkConfig = [
+            'csrf_protection' => ['enabled' => false],
             'secret' => 'S3CRET',
             'test' => true,
             'router' => ['utf8' => true],
diff --git a/src/LiveComponent/tests/Fixtures/templates/csrf.html.twig b/src/LiveComponent/tests/Fixtures/templates/csrf.html.twig
deleted file mode 100644
index 314377bc..00000000
--- a/src/LiveComponent/tests/Fixtures/templates/csrf.html.twig
+++ /dev/null
@@ -1,1 +0,0 @@
-{{ component('disabled_csrf') }}
diff --git a/src/LiveComponent/tests/Fixtures/templates/render_form_with_collection_type.html.twig b/src/LiveComponent/tests/Fixtures/templates/render_form_with_collection_type.html.twig
index 5ce0cc1a..9c8eb4fc 100644
--- a/src/LiveComponent/tests/Fixtures/templates/render_form_with_collection_type.html.twig
+++ b/src/LiveComponent/tests/Fixtures/templates/render_form_with_collection_type.html.twig
@@ -1,3 +1,1 @@
-{{ component('form_with_collection_type', {
-    enableCsrf: true,
-}) }}
+{{ component('form_with_collection_type') }}
diff --git a/src/LiveComponent/tests/Functional/Controller/BatchActionControllerTest.php b/src/LiveComponent/tests/Functional/Controller/BatchActionControllerTest.php
index 0de3b868..6151cff6 100644
--- a/src/LiveComponent/tests/Functional/Controller/BatchActionControllerTest.php
+++ b/src/LiveComponent/tests/Functional/Controller/BatchActionControllerTest.php
@@ -52,7 +52,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             'args' => ['what' => 'first'],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value')],
                 ]);
             })
             ->assertSee('initial')
@@ -72,7 +71,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             ],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value')],
                 ]);
             })
             ->assertSee('initial')
@@ -113,7 +111,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             ],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $rootElement->attr('data-live-csrf-value')],
                 ]);
             })
             ->assertOn('/alt/alternate_route/_batch')
@@ -122,19 +119,6 @@ final class BatchActionControllerTest extends KernelTestCase
         ;
     }
 
-    public function testCsrfTokenIsChecked(): void
-    {
-        $dehydrated = $this->dehydrateComponent($this->mountComponent('with_actions'));
-
-        $this->browser()
-            ->post('/_components/with_actions/_batch', ['json' => [
-                'props' => $dehydrated->getProps(),
-                'actions' => [],
-            ]])
-            ->assertStatus(400)
-        ;
-    }
-
     public function testRedirect(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('with_actions'));
@@ -165,7 +149,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             ],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value')],
                 ]);
             })
             ->assertRedirectedTo('/')
@@ -203,7 +186,6 @@ final class BatchActionControllerTest extends KernelTestCase
                     ],
                     'headers' => [
                         'Accept' => ['application/vnd.live-component+html'],
-                        'X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value'),
                     ],
                 ]);
             })
@@ -241,7 +223,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             ],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value')],
                 ]);
             })
         ;
@@ -276,7 +257,6 @@ final class BatchActionControllerTest extends KernelTestCase
                             ],
                         ]),
                     ],
-                    'headers' => ['X-CSRF-TOKEN' => $crawler->filter('ul')->first()->attr('data-live-csrf-value')],
                 ]);
             })
         ;
diff --git a/src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php b/src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php
index 564a83c6..74d5f975 100644
--- a/src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php
+++ b/src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php
@@ -44,7 +44,6 @@ final class AddLiveAttributesSubscriberTest extends KernelTestCase
 
         $this->assertSame('live', $div->attr('data-controller'));
         $this->assertSame('/_components/component_with_writable_props', $div->attr('data-live-url-value'));
-        $this->assertNotNull($div->attr('data-live-csrf-value'));
         $this->assertCount(4, $props);
         $this->assertSame(5, $props['max']);
         $this->assertSame(1, $props['count']);
@@ -66,24 +65,9 @@ final class AddLiveAttributesSubscriberTest extends KernelTestCase
 
         $this->assertSame('live', $div->attr('data-controller'));
         $this->assertSame('/_components/custom_attributes', $div->attr('data-live-url-value'));
-        $this->assertNotNull($div->attr('data-live-csrf-value'));
         $this->assertArrayHasKey('@checksum', $props);
     }
 
-    public function testCanDisableCsrf(): void
-    {
-        $div = $this->browser()
-            ->visit('/render-template/csrf')
-            ->assertSuccessful()
-            ->crawler()
-            ->filter('div')
-        ;
-
-        $this->assertSame('live', $div->attr('data-controller'));
-        $this->assertSame('/_components/disabled_csrf', $div->attr('data-live-url-value'));
-        $this->assertNull($div->attr('data-live-csrf-value'));
-    }
-
     public function testItAddsIdAndFingerprintToChildComponent(): void
     {
         $templateName = 'components/todo_list.html.twig';
@@ -174,7 +158,6 @@ final class AddLiveAttributesSubscriberTest extends KernelTestCase
 
         $this->assertSame('live', $div->attr('data-controller'));
         $this->assertSame('http://localhost/_components/with_absolute_url', $div->attr('data-live-url-value'));
-        $this->assertNotNull($div->attr('data-live-csrf-value'));
         $this->assertCount(3, $props);
         $this->assertArrayHasKey('@checksum', $props);
         $this->assertArrayHasKey('@attributes', $props);
@@ -185,19 +168,16 @@ final class AddLiveAttributesSubscriberTest extends KernelTestCase
 
     public function testAbsoluteUrlWithLiveQueryProp()
     {
-        $token = null;
         $props = [];
         $div = $this->browser()
             ->get('/render-template/render_with_absolute_url?count=1')
             ->assertSuccessful()
             ->assertContains('Count: 1')
-            ->use(function (Crawler $crawler) use (&$token, &$props) {
+            ->use(function (Crawler $crawler) use (&$props) {
                 $div = $crawler->filter('div')->first();
-                $token = $div->attr('data-live-csrf-value');
                 $props = json_decode($div->attr('data-live-props-value'), true);
             })
             ->post('http://localhost/_components/with_absolute_url/increase', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $props])],
             ])
             ->assertContains('Count: 2')
@@ -209,7 +189,6 @@ final class AddLiveAttributesSubscriberTest extends KernelTestCase
 
         $this->assertSame('live', $div->attr('data-controller'));
         $this->assertSame('http://localhost/_components/with_absolute_url', $div->attr('data-live-url-value'));
-        $this->assertNotNull($div->attr('data-live-csrf-value'));
         $this->assertCount(3, $props);
         $this->assertArrayHasKey('@checksum', $props);
         $this->assertArrayHasKey('@attributes', $props);
diff --git a/src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php b/src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php
index e4a87734..9ac59da9 100644
--- a/src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php
+++ b/src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php
@@ -108,7 +108,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
                 ]
             )
         );
-        $token = null;
 
         $this->browser()
             ->throwExceptions()
@@ -122,12 +121,7 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             ->assertSuccessful()
             ->assertHeaderContains('Content-Type', 'html')
             ->assertContains('Count: 1')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
-            })
             ->post('/_components/component2/increase', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
             ->assertSuccessful()
@@ -140,7 +134,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
     public function testCanExecuteComponentActionWithAlternateRoute(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('alternate_route'));
-        $token = null;
 
         $this->browser()
             ->throwExceptions()
@@ -153,12 +146,7 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             ])
             ->assertSuccessful()
             ->assertContains('count: 0')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
-            })
             ->post('/alt/alternate_route/increase', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
             ->assertSuccessful()
@@ -183,77 +171,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
         ;
     }
 
-    public function testMissingCsrfTokenForComponentActionFails(): void
-    {
-        $this->browser()
-            ->post('/_components/component2/increase')
-            ->assertStatus(400)
-        ;
-
-        try {
-            $this->browser()
-                ->throwExceptions()
-                ->post('/_components/component2/increase')
-            ;
-        } catch (BadRequestHttpException $e) {
-            $this->assertSame('Invalid CSRF token.', $e->getMessage());
-
-            return;
-        }
-
-        $this->fail('Expected exception not thrown.');
-    }
-
-    public function testInvalidCsrfTokenForComponentActionFails(): void
-    {
-        $this->browser()
-            ->post('/_components/component2/increase', [
-                'headers' => ['X-CSRF-TOKEN' => 'invalid'],
-            ])
-            ->assertStatus(400)
-        ;
-
-        try {
-            $this->browser()
-                ->throwExceptions()
-                ->post('/_components/component2/increase', [
-                    'headers' => ['X-CSRF-TOKEN' => 'invalid'],
-                ])
-            ;
-        } catch (BadRequestHttpException $e) {
-            $this->assertSame('Invalid CSRF token.', $e->getMessage());
-
-            return;
-        }
-
-        $this->fail('Expected exception not thrown.');
-    }
-
-    public function testDisabledCsrfTokenForComponentDoesNotFail(): void
-    {
-        $dehydrated = $this->dehydrateComponent($this->mountComponent('disabled_csrf'));
-
-        $this->browser()
-            ->throwExceptions()
-            ->post('/_components/disabled_csrf', [
-                'body' => [
-                    'data' => json_encode([
-                        'props' => $dehydrated->getProps(),
-                    ]),
-                ],
-            ])
-            ->assertSuccessful()
-            ->assertHeaderContains('Content-Type', 'html')
-            ->assertContains('Count: 1')
-            ->post('/_components/disabled_csrf/increase', [
-                'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
-            ])
-            ->assertSuccessful()
-            ->assertHeaderContains('Content-Type', 'html')
-            ->assertContains('Count: 2')
-        ;
-    }
-
     public function testPreReRenderHookOnlyExecutedDuringAjax(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('component2'));
@@ -341,20 +258,13 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             )
         );
 
-        $token = null;
-
         $this->browser()
             ->visit('/render-template/render_multiple_embedded_with_blocks')
             ->assertSuccessful()
             ->assertSeeIn('#component1', 'Overridden content from component 1')
             ->assertSeeIn('#component2', 'Overridden content from component 2 on same line - count: 1')
             ->assertSeeIn('#component3', 'PreReRenderCalled: No')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->eq(1)->attr('data-live-csrf-value');
-            })
             ->post('/_components/component2/increase', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
             ->assertSuccessful()
@@ -380,20 +290,13 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             )
         );
 
-        $token = null;
-
         $this->browser()
             ->visit('/render-namespaced-template/render_multiple_embedded_with_blocks')
             ->assertSuccessful()
             ->assertSeeIn('#component1', 'Overridden content from component 1')
             ->assertSeeIn('#component2', 'Overridden content from component 2 on same line - count: 1')
             ->assertSeeIn('#component3', 'PreReRenderCalled: No')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->eq(1)->attr('data-live-csrf-value');
-            })
             ->post('/_components/component2/increase', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
             ->assertSuccessful()
@@ -405,7 +308,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
     public function testCanRedirectFromComponentAction(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('component2'));
-        $token = null;
 
         $this->browser()
             ->throwExceptions()
@@ -417,14 +319,9 @@ final class LiveComponentSubscriberTest extends KernelTestCase
                 ],
             ])
             ->assertSuccessful()
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
-            })
             ->interceptRedirects()
             // with no custom header, it redirects like a normal browser
             ->post('/_components/component2/redirect', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
             ->assertRedirectedTo('/')
@@ -433,7 +330,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             ->post('/_components/component2/redirect', [
                 'headers' => [
                     'Accept' => 'application/vnd.live-component+html',
-                    'X-CSRF-TOKEN' => $token,
                 ],
                 'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
             ])
@@ -447,7 +343,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
     public function testInjectsLiveArgs(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('component6'));
-        $token = null;
 
         $arguments = ['arg1' => 'hello', 'arg2' => 666, 'custom' => '33.3'];
         $this->browser()
@@ -464,12 +359,7 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             ->assertContains('Arg1: not provided')
             ->assertContains('Arg2: not provided')
             ->assertContains('Arg3: not provided')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
-            })
             ->post('/_components/component6/inject', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => [
                     'data' => json_encode([
                         'props' => $dehydrated->getProps(),
@@ -524,7 +414,6 @@ final class LiveComponentSubscriberTest extends KernelTestCase
     public function testCanInjectSecurityUserIntoAction(): void
     {
         $dehydrated = $this->dehydrateComponent($this->mountComponent('with_security'));
-        $token = null;
 
         $this->browser()
             ->actingAs(new InMemoryUser('kevin', 'pass', ['ROLE_USER']))
@@ -537,13 +426,8 @@ final class LiveComponentSubscriberTest extends KernelTestCase
             ])
             ->assertSuccessful()
             ->assertNotSee('username: kevin')
-            ->use(function (Crawler $crawler) use (&$token) {
-                // get a valid token to use for actions
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
-            })
             ->throwExceptions()
             ->post('/_components/with_security/setUsername', [
-                'headers' => ['X-CSRF-TOKEN' => $token],
                 'body' => [
                     'data' => json_encode([
                         'props' => $dehydrated->getProps(),
diff --git a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
index 59f886fd..ca0bb583 100644
--- a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
+++ b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
@@ -46,13 +46,11 @@ class ComponentWithFormTest extends KernelTestCase
             'blog_post_form.content' => 'changed description by user',
             'validatedFields' => ['blog_post_form.content'],
         ];
-        $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
 
         $crawler = $browser
             // post to action, which will add a new embedded comment
             ->post('/_components/form_with_collection_type/addComment', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps, 'updated' => $updatedProps])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(422)
             // look for original embedded form
@@ -82,13 +80,11 @@ class ComponentWithFormTest extends KernelTestCase
         // fake that this field was being validated
         $updatedProps = ['validatedFields' => $dehydratedProps['validatedFields']];
         $updatedProps['validatedFields'][] = 'blog_post_form.comments.0.content';
-        $token = $div->attr('data-live-csrf-value');
 
         $crawler = $browser
             // post to action, which will remove the original embedded comment
             ->post('/_components/form_with_collection_type/removeComment', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps, 'updated' => $updatedProps, 'args' => ['index' => '0']])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(422)
             // the original embedded form should be gone
@@ -110,7 +106,6 @@ class ComponentWithFormTest extends KernelTestCase
             // empty the collection
             ->post('/_components/form_with_collection_type/removeComment', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps, 'args' => ['index' => '1']])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(422)
             ->assertNotContains('<textarea id="blog_post_form_comments_')
@@ -328,7 +323,6 @@ class ComponentWithFormTest extends KernelTestCase
 
         $div = $crawler->filter('[data-controller="live"]');
         $dehydratedProps = json_decode($div->attr('data-live-props-value'), true);
-        $token = $div->attr('data-live-csrf-value');
 
         $browser
             ->post('/_components/form_with_many_different_fields_type/submitAndResetForm', [
@@ -336,7 +330,6 @@ class ComponentWithFormTest extends KernelTestCase
                     'props' => $dehydratedProps,
                     'updated' => ['form.textarea' => 'short'],
                 ])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(200)
             ->assertContains('<textarea id="form_textarea" name="form[textarea]" required="required"></textarea>')
@@ -346,7 +339,6 @@ class ComponentWithFormTest extends KernelTestCase
         $browser
             ->post('/_components/form_with_many_different_fields_type/resetFormWithoutSubmitting', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(200)
             ->assertNotContains('textarea is too long')
@@ -358,7 +350,6 @@ class ComponentWithFormTest extends KernelTestCase
     {
         $dehydratedProps = $this->dehydrateComponent($this->mountComponent('form_with_live_collection_type'))->getProps();
         $updatedProps = [];
-        $token = null;
 
         $this->browser()
             ->post('/_components/form_with_live_collection_type', [
@@ -368,18 +359,16 @@ class ComponentWithFormTest extends KernelTestCase
                     ]),
                 ],
             ])
-            ->use(function (Crawler $crawler) use (&$dehydratedProps, &$token, &$updatedProps) {
+            ->use(function (Crawler $crawler) use (&$updatedProps) {
                 // mimic user typing
                 $updatedProps = [
                     'blog_post_form.content' => 'changed description by user',
                     'validatedFields' => ['blog_post_form.content'],
                 ];
-                $token = $crawler->filter('div')->first()->attr('data-live-csrf-value');
             })
             // post to action, which will add a new embedded comment
             ->post('/_components/form_with_live_collection_type/addCollectionItem', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps, 'updated' => $updatedProps, 'args' => ['name' => 'blog_post_form[comments]']])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(422)
             // look for original embedded form
@@ -392,7 +381,7 @@ class ComponentWithFormTest extends KernelTestCase
             ->assertContains('The content field is too short')
             // make sure the title field did not suddenly become validated
             ->assertNotContains('The title field should not be blank')
-            ->use(function (Crawler $crawler) use (&$dehydratedProps, &$token, $updatedProps) {
+            ->use(function (Crawler $crawler) use (&$dehydratedProps) {
                 $div = $crawler->filter('[data-controller="live"]');
                 $dehydratedProps = json_decode($div->attr('data-live-props-value'), true);
                 // make sure the 2nd collection type was initialized, that it didn't
@@ -410,13 +399,11 @@ class ComponentWithFormTest extends KernelTestCase
                     $dehydratedProps['validatedFields'],
                     ['blog_post_form.0.comments.content']
                 )];
-                $token = $div->attr('data-live-csrf-value');
             })
 
             // post to action, which will remove the original embedded comment
             ->post('/_components/form_with_live_collection_type/removeCollectionItem', [
                 'body' => ['data' => json_encode(['props' => $dehydratedProps, 'updated' => $updatedProps, 'args' => ['name' => 'blog_post_form[comments]', 'index' => '0']])],
-                'headers' => ['X-CSRF-TOKEN' => $token],
             ])
             ->assertStatus(422)
             // the original embedded form should be gone
diff --git a/src/LiveComponent/tests/Integration/EventListener/DataModelPropsSubscriberTest.php b/src/LiveComponent/tests/Integration/EventListener/DataModelPropsSubscriberTest.php
index 34b1326a..1d04d03d 100644
--- a/src/LiveComponent/tests/Integration/EventListener/DataModelPropsSubscriberTest.php
+++ b/src/LiveComponent/tests/Integration/EventListener/DataModelPropsSubscriberTest.php
@@ -22,8 +22,6 @@ final class DataModelPropsSubscriberTest extends KernelTestCase
 
     public function testDataModelPropsAreSharedToChild(): void
     {
-        $this->fakeSession();
-
         /** @var ComponentRenderer $renderer */
         $renderer = self::getContainer()->get('ux.twig_component.component_renderer');
 
@@ -43,8 +41,6 @@ final class DataModelPropsSubscriberTest extends KernelTestCase
 
     public function testDataModelPropsAreAvailableInEmbeddedComponents(): void
     {
-        $this->fakeSession();
-
         $templateName = 'components/parent_component_data_model.html.twig';
         $obscuredName = '684c45bf85d3461dbe587407892e59d8';
         $this->addTemplateMap($obscuredName, $templateName);
@@ -59,14 +55,4 @@ final class DataModelPropsSubscriberTest extends KernelTestCase
         $this->assertStringContainsString('<textarea data-model="content">default content on mount</textarea>', $html);
         $this->assertStringContainsString('<input data-model="content" value="default content on mount" />', $html);
     }
-
-    private function fakeSession(): void
-    {
-        // work around so that a session is available so CSRF doesn't fail
-        $session = self::getContainer()->get('session.factory')->createSession();
-        $request = Request::create('/');
-        $request->setSession($session);
-        $requestStack = self::getContainer()->get('request_stack');
-        $requestStack->push($request);
-    }
 }
diff --git a/src/LiveComponent/tests/Unit/Util/LiveAttributesCollectionTest.php b/src/LiveComponent/tests/Unit/Util/LiveAttributesCollectionTest.php
index ba933cf9..a8f9cbfa 100644
--- a/src/LiveComponent/tests/Unit/Util/LiveAttributesCollectionTest.php
+++ b/src/LiveComponent/tests/Unit/Util/LiveAttributesCollectionTest.php
@@ -27,7 +27,6 @@ class LiveAttributesCollectionTest extends KernelTestCase
         $collection->setFingerprint('the-fingerprint');
         $collection->setProps(['the' => 'props']);
         $collection->setUrl('the-live-url');
-        $collection->setCsrf('the-csrf-token');
         $collection->setListeners(['event_name' => 'theActionName']);
         $collection->setEventsToEmit([
             [
@@ -55,7 +54,6 @@ class LiveAttributesCollectionTest extends KernelTestCase
             'data-live-fingerprint-value' => 'the-fingerprint',
             'data-live-props-value' => '&#x7B;&quot;the&quot;&#x3A;&quot;props&quot;&#x7D;',
             'data-live-url-value' => 'the-live-url',
-            'data-live-csrf-value' => 'the-csrf-token',
             'data-live-listeners-value' => '&#x7B;&quot;event_name&quot;&#x3A;&quot;theActionName&quot;&#x7D;',
             'data-live-events-to-emit-value' => '&#x5B;&#x7B;&quot;event&quot;&#x3A;&quot;event_name1&quot;,&quot;data&quot;&#x3A;&#x7B;&quot;the&quot;&#x3A;&quot;data&quot;&#x7D;,&quot;target&quot;&#x3A;&quot;up&quot;,&quot;componentName&quot;&#x3A;&quot;the-component&quot;&#x7D;,&#x7B;&quot;event&quot;&#x3A;&quot;event_name2&quot;,&quot;data&quot;&#x3A;&#x7B;&quot;the&quot;&#x3A;&quot;data&quot;&#x7D;,&quot;target&quot;&#x3A;null,&quot;componentName&quot;&#x3A;null&#x7D;&#x5D;',
             'data-live-query-mapping-value' => '&#x7B;&quot;foo&quot;&#x3A;&#x7B;&quot;name&quot;&#x3A;&quot;foo&quot;&#x7D;,&quot;bar&quot;&#x3A;&#x7B;&quot;name&quot;&#x3A;&quot;bar&quot;&#x7D;&#x7D;',