diff --git a/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php
index 00062f189f9211c904e8310077ef5989effa09a1..46385fa792ebc6e37ffe0d8fa10acfebb89d9f43 100644
--- a/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php
+++ b/typo3/sysext/backend/Tests/Functional/Controller/MfaConfigurationControllerTest.php
@@ -27,7 +27,6 @@ use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -68,12 +67,6 @@ class MfaConfigurationControllerTest extends FunctionalTestCase
         $this->normalizedParams = new NormalizedParams([], [], '', '');
     }
 
-    protected function tearDown(): void
-    {
-        FormProtectionFactory::purgeInstances();
-        parent::tearDown();
-    }
-
     /**
      * @test
      */
diff --git a/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php
index 1bb4fdf0e82280402be2cf3b62440e301a157615..ca59bb603504c2c3879e6a0db9e5aa14c54a6021 100644
--- a/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php
+++ b/typo3/sysext/backend/Tests/Functional/Controller/MfaControllerTest.php
@@ -28,7 +28,6 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Log\Logger;
 use TYPO3\CMS\Core\Page\PageRenderer;
@@ -76,12 +75,6 @@ class MfaControllerTest extends FunctionalTestCase
             ->withAttribute('route', new Route('path', ['packageName' => 'typo3/cms-backend']));
     }
 
-    protected function tearDown(): void
-    {
-        FormProtectionFactory::purgeInstances();
-        parent::tearDown();
-    }
-
     /**
      * @test
      */
diff --git a/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php b/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php
index d72d74b9a1dc7a9d258cda195ec4a76f1cdaa38d..1d2af659cec0975c07289194fb4df26d87793007 100644
--- a/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php
+++ b/typo3/sysext/core/Classes/FormProtection/FormProtectionFactory.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Core\FormProtection;
 
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
@@ -41,17 +42,11 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  */
 class FormProtectionFactory
 {
-    /**
-     * created instances of form protections using the type as array key
-     *
-     * @var array<string, AbstractFormProtection>
-     */
-    protected static array $instances = [];
-
     public function __construct(
         protected readonly FlashMessageService $flashMessageService,
         protected readonly LanguageServiceFactory $languageServiceFactory,
-        protected readonly Registry $registry
+        protected readonly Registry $registry,
+        protected readonly FrontendInterface $runtimeCache
     ) {
     }
 
@@ -65,12 +60,13 @@ class FormProtectionFactory
         if (!in_array($type, ['installtool', 'frontend', 'backend', 'disabled'], true)) {
             $type = 'disabled';
         }
-        if (isset(self::$instances[$type])) {
-            return self::$instances[$type];
+        $identifier = $this->getIdentifierForType($type);
+        if ($this->runtimeCache->has($identifier)) {
+            return $this->runtimeCache->get($identifier);
         }
         $classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $GLOBALS['TYPO3_REQUEST'] ?? null);
-        self::$instances[$type] = self::createInstance(...$classNameAndConstructorArguments);
-        return self::$instances[$type];
+        $this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments));
+        return $this->runtimeCache->get($identifier);
     }
 
     /**
@@ -80,12 +76,13 @@ class FormProtectionFactory
     public function createFromRequest(ServerRequestInterface $request): AbstractFormProtection
     {
         $type = $this->determineTypeFromRequest($request);
-        if (isset(self::$instances[$type])) {
-            return self::$instances[$type];
+        $identifier = $this->getIdentifierForType($type);
+        if ($this->runtimeCache->has($identifier)) {
+            return $this->runtimeCache->get($identifier);
         }
         $classNameAndConstructorArguments = $this->getClassNameAndConstructorArguments($type, $request);
-        self::$instances[$type] = self::createInstance(...$classNameAndConstructorArguments);
-        return self::$instances[$type];
+        $this->runtimeCache->set($identifier, $this->createInstance(...$classNameAndConstructorArguments));
+        return $this->runtimeCache->get($identifier);
     }
 
     /**
@@ -93,13 +90,13 @@ class FormProtectionFactory
      */
     protected function determineTypeFromRequest(ServerRequestInterface $request): string
     {
-        if (self::isInstallToolSession($request)) {
+        if ($this->isInstallToolSession($request)) {
             return 'installtool';
         }
-        if (self::isFrontendSession($request)) {
+        if ($this->isFrontendSession($request)) {
             return 'frontend';
         }
-        if (self::isBackendSession($request)) {
+        if ($this->isBackendSession($request)) {
             return 'backend';
         }
         return 'disabled';
@@ -137,7 +134,7 @@ class FormProtectionFactory
                     BackendFormProtection::class,
                     $user,
                     $this->registry,
-                    self::getMessageClosure(
+                    $this->getMessageClosure(
                         $this->languageServiceFactory->createFromUserPreferences($user),
                         $this->flashMessageService->getMessageQueueByIdentifier(),
                         $isAjaxCall
@@ -151,6 +148,14 @@ class FormProtectionFactory
         ];
     }
 
+    /**
+     * Conveniant method to create a deterministic cache identifier.
+     */
+    protected function getIdentifierForType(string $type): string
+    {
+        return 'formprotection-instance-' . hash('xxh3', $type);
+    }
+
     /**
      * Gets a form protection instance for the requested type or class.
      *
@@ -163,114 +168,82 @@ class FormProtectionFactory
      *                                frontend, backend, installtool
      * @param array<int,mixed> $constructorArguments Arguments for the class-constructor
      * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection the requested instance
+     *
+     * @deprecated since v12, will be removed in v13 together with createForTypeWithArguments. Use a instance of FormProtectionFactory directly.
+     * @see self::createFromRequest()
+     * @see self::createForType()
+     * @see self::createForClass()
      */
     public static function get($classNameOrType = 'default', ...$constructorArguments)
     {
-        if (isset(self::$instances[$classNameOrType])) {
-            return self::$instances[$classNameOrType];
-        }
-        if ($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend') {
-            $classNameAndConstructorArguments = self::getClassNameAndConstructorArgumentsByType($classNameOrType);
-            self::$instances[$classNameOrType] = self::createInstance(...$classNameAndConstructorArguments);
-        } else {
-            self::$instances[$classNameOrType] = self::createInstance($classNameOrType, ...$constructorArguments);
+        trigger_error(
+            __METHOD__ . ' will be removed in TYPO3 v13.0. Use a instance of ' . __CLASS__ . ' directly.',
+            E_USER_DEPRECATED
+        );
+        if (($classNameOrType === 'default' || $classNameOrType === 'installtool' || $classNameOrType === 'frontend' || $classNameOrType === 'backend')) {
+            return GeneralUtility::makeInstance(FormProtectionFactory::class)
+                ->createForType($classNameOrType);
         }
-        return self::$instances[$classNameOrType];
+        return GeneralUtility::makeInstance(FormProtectionFactory::class)
+            ->createForClass($classNameOrType, ...$constructorArguments);
     }
 
     /**
-     * Returns the class name and parameters depending on the given type.
-     * If the type cannot be used currently, protection is disabled.
+     * Create a concrete FormProtection implementation, using the provided arguments as constructor arguments.
+     * Should be used instead of FormProtectionFactory::get() is a custom FormProtection implementation must
+     * be instantiated.
      *
-     * @param string $type Valid types: default, installtool, frontend, backend. "default" makes an autodetection on the current state
-     * @return array Array of arguments
+     * For provided core implementation use
+     *
+     *      // auto-detected based on request
+     *      GeneralUtility::makeInstance(FormProtectionFactory::class)
+     *          ->createFromRequest($GLOBAL['TYPO3_REQUEST']);
+     * or
+     *      // concrete implementation
+     *      GeneralUtility::makeInstance(FormProtectionFactory::class)
+     *          ->createForType($type); // 'installtool', 'backend', 'frontend', 'disabled'
+     *
+     * @param string $className Name of a form protection class
+     * @param array<int,mixed> $constructorArguments Arguments for the class-constructor
+     * @deprecated since v12, will be removed in v13 together with get().
      */
-    protected static function getClassNameAndConstructorArgumentsByType($type, ServerRequestInterface $request = null)
+    protected function createForClass(string $className, ...$constructorArguments): AbstractFormProtection
     {
-        if (self::isInstallToolSession($request) && ($type === 'default' || $type === 'installtool')) {
-            $classNameAndConstructorArguments = [
-                InstallToolFormProtection::class,
-            ];
-        } elseif (self::isFrontendSession($request) && ($type === 'default' || $type === 'frontend')) {
-            $classNameAndConstructorArguments = [
-                FrontendFormProtection::class,
-                $GLOBALS['TSFE']->fe_user,
-            ];
-        } elseif (self::isBackendSession($request) && ($type === 'default' || $type === 'backend')) {
-            $isAjaxCall = false;
-            $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? null;
-            if ($request instanceof ServerRequestInterface
-                && (bool)($request->getAttribute('route')?->getOption('ajax'))
-            ) {
-                $isAjaxCall = true;
-            }
-            $classNameAndConstructorArguments = [
-                BackendFormProtection::class,
-                $GLOBALS['BE_USER'],
-                GeneralUtility::makeInstance(Registry::class),
-                self::getMessageClosure(
-                    $GLOBALS['LANG'],
-                    GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier(),
-                    $isAjaxCall
-                ),
-            ];
-        } else {
-            // failed to use preferred type, disable form protection
-            $classNameAndConstructorArguments = [
-                DisabledFormProtection::class,
-            ];
+        $identifier = $this->getIdentifierForType($className);
+        if ($this->runtimeCache->has($identifier)) {
+            return $this->runtimeCache->get($identifier);
         }
-        return $classNameAndConstructorArguments;
+        $this->runtimeCache->set($identifier, $this->createInstance($className, ...$constructorArguments));
+        return $this->runtimeCache->get($identifier);
     }
 
     /**
      * Check if we are in the install tool
      */
-    protected static function isInstallToolSession(?ServerRequestInterface $request = null): bool
+    protected function isInstallToolSession(ServerRequestInterface $request): bool
     {
-        $isInstallTool = false;
-        $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? null;
-        if ($request instanceof ServerRequestInterface
-            && (bool)((int)$request->getAttribute('applicationType') & SystemEnvironmentBuilder::REQUESTTYPE_INSTALL)
-        ) {
-            $isInstallTool = true;
-        }
-        return $isInstallTool;
+        return (bool)((int)$request->getAttribute('applicationType') & SystemEnvironmentBuilder::REQUESTTYPE_INSTALL);
     }
 
     /**
      * Checks if a user is logged in and the session is active.
      */
-    protected static function isBackendSession(?ServerRequestInterface $request = null): bool
+    protected function isBackendSession(ServerRequestInterface $request): bool
     {
-        if ($request instanceof ServerRequestInterface) {
-            $user = $request->getAttribute('backend.user', $GLOBALS['BE_USER'] ?? null);
-        } else {
-            $user = $GLOBALS['BE_USER'] ?? null;
-        }
+        $user = $request->getAttribute('backend.user', $GLOBALS['BE_USER'] ?? null);
         return $user instanceof BackendUserAuthentication && isset($user->user['uid']);
     }
 
     /**
      * Checks if a frontend user is logged in and the session is active.
      */
-    protected static function isFrontendSession(?ServerRequestInterface $request = null): bool
+    protected function isFrontendSession(ServerRequestInterface $request): bool
     {
-        if ($request instanceof ServerRequestInterface) {
-            $user = $request->getAttribute('frontend.user');
-        } else {
-            $user = ($GLOBALS['TSFE'] ?? null) instanceof TypoScriptFrontendController ? $GLOBALS['TSFE']->fe_user : null;
-        }
+        $user = $request->getAttribute('frontend.user');
         return $user instanceof FrontendUserAuthentication && isset($user->user['uid']);
     }
 
-    /**
-     * @param LanguageService $languageService
-     * @param FlashMessageQueue $messageQueue
-     * @param bool $isAjaxCall
-     * @return \Closure
-     */
-    protected static function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, bool $isAjaxCall)
+    protected function getMessageClosure(LanguageService $languageService, FlashMessageQueue $messageQueue, bool $isAjaxCall): \Closure
     {
         return static function () use ($languageService, $messageQueue, $isAjaxCall) {
             $flashMessage = GeneralUtility::makeInstance(
@@ -288,12 +261,11 @@ class FormProtectionFactory
      * Creates an instance for the requested class $className
      * and stores it internally.
      *
-     * @param string $className
+     * @param class-string $className
      * @param array<int,mixed> $constructorArguments
      * @throws \InvalidArgumentException
-     * @return AbstractFormProtection
      */
-    protected static function createInstance($className, ...$constructorArguments)
+    protected function createInstance(string $className, ...$constructorArguments): AbstractFormProtection
     {
         if (!class_exists($className)) {
             throw new \InvalidArgumentException('$className must be the name of an existing class, but actually was "' . $className . '".', 1285352962);
@@ -309,20 +281,14 @@ class FormProtectionFactory
      * Purges all existing instances.
      *
      * This function is particularly useful when cleaning up in unit testing.
+     *
+     * @deprecated since v12, will be removed in v13. Internal cache has been replaced by runtime cache.
      */
-    public static function purgeInstances()
-    {
-        foreach (self::$instances as $key => $instance) {
-            unset(self::$instances[$key]);
-        }
-    }
-
-    /**
-     * Only used in test for non-static calls
-     * @internal
-     */
-    public function clearInstances(): void
+    public static function purgeInstances(): void
     {
-        self::$instances = [];
+        trigger_error(
+            __METHOD__ . ' will be removed in TYPO3 v13.0. Cache has been replaced with runtime cache.',
+            E_USER_DEPRECATED
+        );
     }
 }
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index 94846244e7a5f7ebda4d18143014edf2fd4a6c40..d5860759c84b61340d1095a989942ddd34bbbed5 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -23,6 +23,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\Console\Command\HelpCommand;
 use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher;
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
@@ -512,6 +513,7 @@ class ServiceProvider extends AbstractServiceProvider
                 $container->get(Messaging\FlashMessageService::class),
                 $container->get(Localization\LanguageServiceFactory::class),
                 $container->get(Registry::class),
+                $container->get(CacheManager::class)->getCache('runtime'),
             ]
         );
     }
diff --git a/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst
new file mode 100644
index 0000000000000000000000000000000000000000..49d43fb0732ab1f66d7a584d6e967ff0fc4fe91c
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.1/Deprecation-99098-StaticUsageOfFormProtectionFactory.rst
@@ -0,0 +1,140 @@
+.. include:: /Includes.rst.txt
+
+.. _deprecation-99098-1668546853:
+
+===========================================================
+Deprecation: #99098 - Static usage of FormProtectionFactory
+===========================================================
+
+See :issue:`99098`
+
+Description
+===========
+
+:php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory` has been
+constructed in a static class manner in TYPO3 v6.2, using a static property
+based instance cache to avoid recreating instances for a specific typed
+FormProtection implementation. This design made it impossible to retrieve an
+instance of this class via dependency injection. Another side-effect was that
+ensuring properly cleared state between tests has been hard and often
+populated to other tests and thus influencing them.
+
+To mitigate these issues, :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory`
+is now transformed to a non-static class usage with injected services
+and the core runtime cache, removing the static property cache.
+
+Based on these changes, the old static methods :php:`get()` and
+:php:`purgeInstances()` are now deprecated.
+
+There are two general ways to get a specific FormProtection implementation:
+
+* auto-detected from request: :php:`$formProtectionFactory->createFromRequest()`
+* create for a specific type: :php:`$formProtectionFactory->createForType()`
+
+Possible types for :php:`$formProtectionFactory->createForType()` are `frontend`
+`backend`, `installtool` or `disabled`.
+
+
+Impact
+======
+
+Using any of following class methods
+
+* :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()`
+* :php:`\TYPO3\CMS\Core\FormProtection\FormProtectionFactory::purgeInstances()`
+
+will trigger a PHP deprecation notice and will throw a fatal PHP error in
+TYPO3 v13.
+
+
+Affected installations
+======================
+
+The extension scanner will find extensions calling :php:`FormProtectionFactory::get()`
+or :php:`FormProtectionFactory::purgeInstances()` as "strong" matches.
+
+
+Migration
+=========
+
+Provided implementation by TYPO3 core
+-------------------------------------
+
+Before
+
+..  code-block:: php
+
+    // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+
+    // BackendFormProtection
+    $formProtection = FormProtectionFactory::get(BackendFormProtection::class);
+    $formProtection = FormProtectionFactory::get('backend');
+
+    // FrontendFormProtection
+    $formProtection = FormProtectionFactory::get(FrontedFormProtection::class);
+    $formProtection = FormProtectionFactory::get('frontend');
+
+    // Default / Disabled FormProtection
+    $formProtection = FormProtectionFactory::get(DisabledFormProtection::class);
+    $formProtection = FormProtectionFactory::get('default');
+
+After
+
+It is recommended to use :php:`FormProtectionFactory->createForRequest()` to
+auto-detect which type is needed and return the corresponding instance:
+
+..  code-block:: php
+
+    // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+
+    // Better: Get FormProtectionFactory injected by DI.
+    $formProtectionFactory = GeneralUtility::makeInstance(FormProtectionFactory::class);
+    // $request is assumed to be available, for instance in controller classes.
+    $formProtection = $formProtectionFactory->createFromRequest($request);
+
+To create a specific type directly, using following replacements:
+
+..  code-block:: php
+
+    // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+    // Better: Get FormProtectionFactory injected by DI.
+    $formProtectionFactory = GeneralUtility::makeInstance(FormProtectionFactory::class);
+
+    // BackendFormProtection
+    $formProtection = $formProtectionFactory->createFromType('backend');
+
+    // FrontendFormProtection
+    $formProtection = $formProtectionFactory->createFromType('frontend');
+
+    // Default / Disabled FormProtection
+    $formProtection = $formProtectionFactory->createFromType('disabled');
+
+Custom FormProtection based implementation
+------------------------------------------
+
+Before
+
+..  code-block:: php
+
+    // use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+
+    $formProtection = FormProtectionFactory::get(
+        Vendor\ExtensionKey\FormProtection\CustomFormProtection::class,
+        $customService,
+        'someDirectValue',
+        ...
+    );
+
+After
+
+..  code-block:: php
+
+    // Create an instance of the class yourself, take care of an
+    // instance cache if needed.
+    GeneralUtility::makeInstance(
+        Vendor\ExtensionKey\FormProtection\CustomFormProtection::class,
+        $constructorArguments
+    );
+
+
+.. index:: PHP-API, FullyScanned, ext:core
diff --git a/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php b/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php
index 014f980db276dfea615213159e4fa624f9ef2d0c..83d58079f63b7c7743f0daef8721ae2aec72b416 100644
--- a/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php
+++ b/typo3/sysext/core/Tests/Functional/Authentication/Mfa/Provider/RecoveryCodesProviderTest.php
@@ -25,7 +25,6 @@ use TYPO3\CMS\Core\Authentication\Mfa\MfaViewType;
 use TYPO3\CMS\Core\Authentication\Mfa\Provider\RecoveryCodes;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Http\PropagateResponseException;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
@@ -61,12 +60,6 @@ class RecoveryCodesProviderTest extends FunctionalTestCase
         $this->subject = $this->get(MfaProviderRegistry::class)->getProvider('recovery-codes');
     }
 
-    protected function tearDown(): void
-    {
-        FormProtectionFactory::purgeInstances();
-        parent::tearDown();
-    }
-
     /**
      * @test
      */
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
index a67ed5824ea009a0d40ddad585f37a946de67adf..d24e57e27d64bd1d7e700392512e697e25915097 100644
--- a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
@@ -20,6 +20,8 @@ namespace TYPO3\CMS\Core\Tests\Unit\Authentication;
 use Psr\Log\NullLogger;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Authentication\IpLocker;
+use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
+use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
@@ -40,12 +42,6 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 class BackendUserAuthenticationTest extends UnitTestCase
 {
-    public function tearDown(): void
-    {
-        FormProtectionFactory::purgeInstances();
-        parent::tearDown();
-    }
-
     /**
      * @test
      */
@@ -63,10 +59,12 @@ class BackendUserAuthenticationTest extends UnitTestCase
         $formProtectionMock = $this->createMock(BackendFormProtection::class);
         $formProtectionMock->expects(self::once())->method('clean');
 
+        $runtimeCache = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()]));
         $formProtectionFactory = new FormProtectionFactory(
             $this->createMock(FlashMessageService::class),
             $this->createMock(LanguageServiceFactory::class),
-            $this->createMock(Registry::class)
+            $this->createMock(Registry::class),
+            $runtimeCache
         );
         GeneralUtility::addInstance(FormProtectionFactory::class, $formProtectionFactory);
         GeneralUtility::addInstance(BackendFormProtection::class, $formProtectionMock);
diff --git a/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php b/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php
index 7f0537106d331e410cf4595ebbbb3b1a5392fba5..a31b3c3ec6c5e5e2953eafcb94198136d90b1280 100644
--- a/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php
+++ b/typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php
@@ -17,8 +17,12 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Tests\Unit\FormProtection;
 
+use Psr\Log\NullLogger;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Cache\Frontend\NullFrontend;
+use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
 use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
 use TYPO3\CMS\Core\FormProtection\DisabledFormProtection;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
@@ -34,9 +38,11 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 class FormProtectionFactoryTest extends UnitTestCase
 {
     protected FormProtectionFactory $subject;
+    protected FrontendInterface $runtimeCacheMock;
 
     protected function setUp(): void
     {
+        $this->runtimeCacheMock = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()]));
         $this->subject = new FormProtectionFactory(
             new FlashMessageService(),
             new LanguageServiceFactory(
@@ -44,104 +50,18 @@ class FormProtectionFactoryTest extends UnitTestCase
                 $this->createMock(LocalizationFactory::class),
                 new NullFrontend('null')
             ),
-            new Registry()
+            new Registry(),
+            $this->runtimeCacheMock
         );
         parent::setUp();
     }
 
     protected function tearDown(): void
     {
-        FormProtectionFactory::purgeInstances();
+        $this->runtimeCacheMock->flush();
         parent::tearDown();
     }
 
-    /////////////////////////
-    // Tests concerning get
-    /////////////////////////
-    /**
-     * @test
-     */
-    public function getForNotExistingClassThrowsException(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1285352962);
-
-        FormProtectionFactory::get('noSuchClass');
-    }
-
-    /**
-     * @test
-     */
-    public function getForClassThatIsNoFormProtectionSubclassThrowsException(): void
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1285353026);
-
-        FormProtectionFactory::get(self::class);
-    }
-
-    /**
-     * @test
-     */
-    public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection(): void
-    {
-        $userMock = $this->createMock(BackendUserAuthentication::class);
-        $userMock->user = ['uid' => 4711];
-        self::assertInstanceOf(
-            BackendFormProtection::class,
-            FormProtectionFactory::get(
-                BackendFormProtection::class,
-                $userMock,
-                $this->createMock(Registry::class)
-            )
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance(): void
-    {
-        $userMock = $this->createMock(BackendUserAuthentication::class);
-        $userMock->user = ['uid' => 4711];
-        $arguments = [
-            BackendFormProtection::class,
-            $userMock,
-            $this->createMock(Registry::class),
-        ];
-        self::assertSame(
-            FormProtectionFactory::get(...$arguments),
-            FormProtectionFactory::get(...$arguments)
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getForTypeInstallToolReturnsInstallToolFormProtection(): void
-    {
-        self::assertInstanceOf(
-            InstallToolFormProtection::class,
-            FormProtectionFactory::get(InstallToolFormProtection::class)
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance(): void
-    {
-        self::assertSame(FormProtectionFactory::get(InstallToolFormProtection::class), FormProtectionFactory::get(InstallToolFormProtection::class));
-    }
-
-    /**
-     * @test
-     */
-    public function getForTypesInstallToolAndDisabledReturnsDifferentInstances(): void
-    {
-        self::assertNotSame(FormProtectionFactory::get(InstallToolFormProtection::class), FormProtectionFactory::get(DisabledFormProtection::class));
-    }
-
     /**
      * @test
      */
@@ -225,7 +145,8 @@ class FormProtectionFactoryTest extends UnitTestCase
         $formProtection = $this->subject->createForType('backend');
         // User is now logged in, but we still get the disabled form protection due to the "singleton" concept
         self::assertInstanceOf(DisabledFormProtection::class, $formProtection);
-        $this->subject->clearInstances();
+        // we need to manually flush this here, aas next test should expect a cleared state.
+        $this->runtimeCacheMock->flush();
         $formProtection = $this->subject->createForType('backend');
         // User is now logged in, now we get a backend form protection, due to the "purge instance" concept.
         self::assertInstanceOf(BackendFormProtection::class, $formProtection);
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php b/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6aabb4d687413bd84b9e1f1180988e70c2dbac65
--- /dev/null
+++ b/typo3/sysext/core/Tests/UnitDeprecated/FormProtection/FormProtectionFactoryTest.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\UnitDeprecated\FormProtection;
+
+use Psr\Log\NullLogger;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Cache\Frontend\NullFrontend;
+use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
+use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
+use TYPO3\CMS\Core\FormProtection\DisabledFormProtection;
+use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
+use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
+use TYPO3\CMS\Core\Localization\Locales;
+use TYPO3\CMS\Core\Localization\LocalizationFactory;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Registry;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class FormProtectionFactoryTest extends UnitTestCase
+{
+    protected FormProtectionFactory $subject;
+    protected FrontendInterface $runtimeCacheMock;
+
+    protected function setUp(): void
+    {
+        $this->runtimeCacheMock = new VariableFrontend('null', new TransientMemoryBackend('null', ['logger' => new NullLogger()]));
+        $this->subject = new FormProtectionFactory(
+            new FlashMessageService(),
+            new LanguageServiceFactory(
+                new Locales(),
+                $this->createMock(LocalizationFactory::class),
+                new NullFrontend('null')
+            ),
+            new Registry(),
+            $this->runtimeCacheMock
+        );
+        parent::setUp();
+    }
+
+    protected function tearDown(): void
+    {
+        $this->runtimeCacheMock->flush();
+        parent::tearDown();
+    }
+
+    /////////////////////////
+    // Tests concerning get
+    /////////////////////////
+    /**
+     * @test
+     */
+    public function getForNotExistingClassThrowsException(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1285352962);
+
+        FormProtectionFactory::get('noSuchClass');
+    }
+
+    /**
+     * @test
+     */
+    public function getForClassThatIsNoFormProtectionSubclassThrowsException(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1285353026);
+
+        FormProtectionFactory::get(self::class);
+    }
+
+    /**
+     * @test
+     */
+    public function getForTypeBackEndWithExistingBackEndReturnsBackEndFormProtection(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        $userMock = $this->createMock(BackendUserAuthentication::class);
+        $userMock->user = ['uid' => 4711];
+        self::assertInstanceOf(
+            BackendFormProtection::class,
+            FormProtectionFactory::get(
+                BackendFormProtection::class,
+                $userMock,
+                $this->createMock(Registry::class)
+            )
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getForTypeBackEndCalledTwoTimesReturnsTheSameInstance(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        $userMock = $this->createMock(BackendUserAuthentication::class);
+        $userMock->user = ['uid' => 4711];
+        $arguments = [
+            BackendFormProtection::class,
+            $userMock,
+            $this->createMock(Registry::class),
+        ];
+        self::assertSame(
+            FormProtectionFactory::get(...$arguments),
+            FormProtectionFactory::get(...$arguments)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getForTypeInstallToolReturnsInstallToolFormProtection(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        self::assertInstanceOf(
+            InstallToolFormProtection::class,
+            FormProtectionFactory::get(InstallToolFormProtection::class)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getForTypeInstallToolCalledTwoTimesReturnsTheSameInstance(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        self::assertSame(
+            FormProtectionFactory::get(InstallToolFormProtection::class),
+            FormProtectionFactory::get(InstallToolFormProtection::class)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getForTypesInstallToolAndDisabledReturnsDifferentInstances(): void
+    {
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        GeneralUtility::addInstance(FormProtectionFactory::class, $this->subject);
+        self::assertNotSame(
+            FormProtectionFactory::get(InstallToolFormProtection::class),
+            FormProtectionFactory::get(DisabledFormProtection::class)
+        );
+    }
+}
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index 06e09a3fe193c125fea7d38b23b24e3b02b35854..0de5e39e5baef563ba8b344f0ee58ab4f9d3beab 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -1408,4 +1408,18 @@ return [
             'Feature-98487-TCAOptionCtrlsecurityignorePageTypeRestriction.rst',
         ],
     ],
+    'TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 99,
+        'restFiles' => [
+            'Deprecation-99098-StaticUsageOfFormProtectionFactory.rst',
+        ],
+    ],
+    'TYPO3\CMS\Core\FormProtection\FormProtectionFactory::purgeInstances' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 99,
+        'restFiles' => [
+            'Deprecation-99098-StaticUsageOfFormProtectionFactory.rst',
+        ],
+    ],
 ];