diff --git a/composer.lock b/composer.lock
index 682f8815e6402bf8f07c13605253e266f8bcc246..7bce767f046291be28ff83072fc8522c063d58df 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8555,12 +8555,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/TYPO3/testing-framework.git",
-                "reference": "ec6ce3a2731aa2dda89c42a4c9af9d1712457795"
+                "reference": "3038638bfb1135ac533069c44c2ecbc4047d8f18"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/ec6ce3a2731aa2dda89c42a4c9af9d1712457795",
-                "reference": "ec6ce3a2731aa2dda89c42a4c9af9d1712457795",
+                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/3038638bfb1135ac533069c44c2ecbc4047d8f18",
+                "reference": "3038638bfb1135ac533069c44c2ecbc4047d8f18",
                 "shasum": ""
             },
             "require": {
@@ -8622,7 +8622,7 @@
                 "issues": "https://github.com/TYPO3/testing-framework/issues",
                 "source": "https://github.com/TYPO3/testing-framework/tree/main"
             },
-            "time": "2022-08-12T17:18:51+00:00"
+            "time": "2022-10-03T18:49:08+00:00"
         }
     ],
     "aliases": [],
@@ -8650,5 +8650,5 @@
     "platform-overrides": {
         "php": "8.1.1"
     },
-    "plugin-api-version": "2.2.0"
+    "plugin-api-version": "2.3.0"
 }
diff --git a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
index d988351ac104c0e1cc12726787144d66d37a791c..9cbe1550efc70a23affe26029bb0c2362b222b74 100644
--- a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
@@ -323,9 +323,10 @@ abstract class AbstractUserAuthentication implements LoggerAwareInterface
             // SameSite "none" needs the secure option (only allowed on HTTPS)
             $isSecure = $cookieSameSite === Cookie::SAMESITE_NONE || GeneralUtility::getIndpEnv('TYPO3_SSL');
             $sessionId = $this->userSession->getIdentifier();
+            $cookieValue = $this->userSession->getJwt();
             $this->setCookie = new Cookie(
                 $this->name,
-                $sessionId,
+                $cookieValue,
                 $cookieExpire,
                 $cookiePath,
                 $cookieDomain,
diff --git a/typo3/sysext/core/Classes/Session/UserSession.php b/typo3/sysext/core/Classes/Session/UserSession.php
index 7958c1c528189911d4f3bc7ff8c38561257fb36f..f86b9badd32942e3f921e413223e40191f8c32c5 100644
--- a/typo3/sysext/core/Classes/Session/UserSession.php
+++ b/typo3/sysext/core/Classes/Session/UserSession.php
@@ -17,6 +17,9 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Session;
 
+use Firebase\JWT\JWT;
+use TYPO3\CMS\Core\Security\JwtTrait;
+
 /**
  * Represents all information about a user's session.
  * A user session can be bound to a frontend / backend user, or an anonymous session based on session data stored
@@ -37,6 +40,8 @@ namespace TYPO3\CMS\Core\Session;
  */
 class UserSession
 {
+    use JwtTrait;
+
     protected const SESSION_UPDATE_GRACE_PERIOD = 61;
     protected string $identifier;
     protected ?int $userId;
@@ -213,6 +218,24 @@ class UserSession
         return $GLOBALS['EXEC_TIME'] > ($this->lastUpdated + self::SESSION_UPDATE_GRACE_PERIOD);
     }
 
+    /**
+     * Gets session ID wrapped in JWT to be used for emitting a new cookie.
+     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature>)>`
+     *
+     * @return string
+     */
+    public function getJwt(): string
+    {
+        // @todo payload could be organized in a new `SessionToken` object
+        return self::encodeHashSignedJwt(
+            [
+                'identifier' => $this->identifier,
+                'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339),
+            ],
+            self::createSigningKeyFromEncryptionKey(UserSession::class)
+        );
+    }
+
     /**
      * Create a new user session based on the provided session record
      *
@@ -252,6 +275,24 @@ class UserSession
         return $userSession;
     }
 
+    /**
+     * Verifies and resolves session ID from submitted cookie value:
+     * `Cookie: <JWT(HS256, [identifier => <session-id>], <signature>)>`
+     *
+     * @param string $cookieValue submitted cookie value
+     * @return non-empty-string|null session ID, null in case verification failed
+     * @throws \Exception
+     * @see getJwt()
+     */
+    public static function resolveIdentifierFromJwt(string $cookieValue): ?string
+    {
+        if ($cookieValue === '') {
+            return null;
+        }
+        $payload = self::decodeJwt($cookieValue, self::createSigningKeyFromEncryptionKey(UserSession::class));
+        return !empty($payload->identifier) && is_string($payload->identifier) ? $payload->identifier : null;
+    }
+
     /**
      * Used internally to store data in the backend
      *
diff --git a/typo3/sysext/core/Classes/Session/UserSessionManager.php b/typo3/sysext/core/Classes/Session/UserSessionManager.php
index 4f347065c4d1b8f148e42081793f1a6b1bc4827f..369433d06f2d8579bbd3a6db9a1047714129a20d 100644
--- a/typo3/sysext/core/Classes/Session/UserSessionManager.php
+++ b/typo3/sysext/core/Classes/Session/UserSessionManager.php
@@ -85,8 +85,13 @@ class UserSessionManager implements LoggerAwareInterface
      */
     public function createFromRequestOrAnonymous(ServerRequestInterface $request, string $cookieName): UserSession
     {
-        $sessionId = (string)($request->getCookieParams()[$cookieName] ?? '');
-        return $this->getSessionFromSessionId($sessionId) ?? $this->createAnonymousSession();
+        try {
+            $cookieValue = (string)($request->getCookieParams()[$cookieName] ?? '');
+            $sessionId = UserSession::resolveIdentifierFromJwt($cookieValue);
+        } catch (\Exception $exception) {
+            $this->logger->debug('Could not resolve session identifier from JWT', ['exception' => $exception]);
+        }
+        return $this->getSessionFromSessionId($sessionId ?? '') ?? $this->createAnonymousSession();
     }
 
     /**
@@ -98,8 +103,13 @@ class UserSessionManager implements LoggerAwareInterface
      */
     public function createFromGlobalCookieOrAnonymous(string $cookieName): UserSession
     {
-        $sessionId = isset($_COOKIE[$cookieName]) ? stripslashes((string)$_COOKIE[$cookieName]) : '';
-        return $this->getSessionFromSessionId($sessionId) ?? $this->createAnonymousSession();
+        try {
+            $cookieValue = isset($_COOKIE[$cookieName]) ? stripslashes((string)$_COOKIE[$cookieName]) : '';
+            $sessionId = UserSession::resolveIdentifierFromJwt($cookieValue);
+        } catch (\Exception $exception) {
+            $this->logger->debug('Could not resolve session identifier from JWT', ['exception' => $exception]);
+        }
+        return $this->getSessionFromSessionId($sessionId  ?? '') ?? $this->createAnonymousSession();
     }
 
     /**
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-94243-SendUserSessionCookiesAsHash-signedJWT.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-94243-SendUserSessionCookiesAsHash-signedJWT.rst
new file mode 100644
index 0000000000000000000000000000000000000000..068bdc0b83e839bf451b4037c1d1b7a3e3bc990a
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-94243-SendUserSessionCookiesAsHash-signedJWT.rst
@@ -0,0 +1,54 @@
+.. include:: /Includes.rst.txt
+
+.. _breaking-94243-1664786038:
+
+===============================================================
+Breaking: #94243 - Send user session cookies as hash-signed JWT
+===============================================================
+
+See :issue:`94243`
+
+Description
+===========
+
+`JSON Web Tokens (JWT) <https://jwt.io/>`__ are used to transport user session
+identifiers in `be_typo_user` and `fe_typo_user` cookies. Using JWT's `HS256`
+(HMAC signed based on SHA256) allows to determine whether a session cookie is
+valid before comparing with server-side stored session data. This enhances the
+overall performance a bit, since sessions cookies would be checked for every
+request to TYPO3's backend and frontend.
+
+JWT handling in PHP is provided by 3rd party package
+`firebase/php-jwt <https://packagist.org/packages/firebase/php-jwt>`__.
+
+
+Impact
+======
+
+Session cookies `be_typo_user` and `fe_typo_user` can be pre-validated without
+querying the database, which can filter invalid requests and might reduce the
+enhances the overall performance a bit.
+
+As a consequence session tokens are not sent "as is" anymore, but are
+wrapped in a corresponding JWT message, which contains the following payload:
+
+* `identifier` reflects the actual session identifier
+* `time` reflects the time of creating the cookie (RFC 3339 format)
+
+
+Affected installations
+======================
+
+All instances using TYPO3 v12 and having custom implementations handling `be_typo_user`
+and `fe_typo_user` cookie values.
+
+
+Migration
+=========
+
+Custom implementations handling `be_typo_user` or `fe_typo_user` cookies,
+have to use the introduced method :php:`\TYPO3\CMS\Core\Session\UserSession::getJwt()`
+instead of existing :php:`\TYPO3\CMS\Core\Session\UserSession::getIdentifier()`.
+
+
+.. index:: Backend, Frontend, NotScanned, ext:core
diff --git a/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php b/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
index a44b8de013407fbadb88be877b73521b93ce2c1b..e900f14636985d3c02b40162259c4b321d28b423 100644
--- a/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
+++ b/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
@@ -20,7 +20,9 @@ namespace TYPO3\CMS\Core\Tests\Unit\Session;
 use Prophecy\Argument;
 use Prophecy\PhpUnit\ProphecyTrait;
 use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\NullLogger;
 use TYPO3\CMS\Core\Authentication\IpLocker;
+use TYPO3\CMS\Core\Security\JwtTrait;
 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
 use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
 use TYPO3\CMS\Core\Session\UserSession;
@@ -30,6 +32,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 class UserSessionManagerTest extends UnitTestCase
 {
     use ProphecyTrait;
+    use JwtTrait;
 
     public function willExpireDataProvider(): array
     {
@@ -88,6 +91,7 @@ class UserSessionManagerTest extends UnitTestCase
      */
     public function createFromRequestOrAnonymousCreatesProperSessionObjects(): void
     {
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = 'secret-encryption-key-test';
         $sessionBackendProphecy = $this->prophesize(SessionBackendInterface::class);
         $sessionBackendProphecy->get('invalid-session')->willThrow(SessionNotFoundException::class);
         $sessionBackendProphecy->get('valid-session')->willReturn([
@@ -102,12 +106,20 @@ class UserSessionManagerTest extends UnitTestCase
             50,
             new IpLocker(0, 0)
         );
+        $subject->setLogger(new NullLogger());
         $request = $this->prophesize(ServerRequestInterface::class);
         $request->getCookieParams()->willReturn([]);
         $anonymousSession = $subject->createFromRequestOrAnonymous($request->reveal(), 'foo');
         self::assertTrue($anonymousSession->isNew());
         self::assertTrue($anonymousSession->isAnonymous());
-        $request->getCookieParams()->willReturn(['foo' => 'invalid-session', 'bar' => 'valid-session']);
+        $validSessionJwt = self::encodeHashSignedJwt(
+            [
+                'identifier' => 'valid-session',
+                'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339),
+            ],
+            self::createSigningKeyFromEncryptionKey(UserSession::class)
+        );
+        $request->getCookieParams()->willReturn(['foo' => 'invalid-session', 'bar' => $validSessionJwt]);
         $anonymousSessionFromInvalidBackendRequest = $subject->createFromRequestOrAnonymous($request->reveal(), 'foo');
         self::assertTrue($anonymousSessionFromInvalidBackendRequest->isNew());
         self::assertTrue($anonymousSessionFromInvalidBackendRequest->isAnonymous());
diff --git a/typo3/sysext/core/Tests/Unit/Session/UserSessionTest.php b/typo3/sysext/core/Tests/Unit/Session/UserSessionTest.php
index 632c2c2271190f8dace7c36fb7ba759f207a2bd5..24f8f1a10ad9f99cbe4cffbee4d6f94e325c275a 100644
--- a/typo3/sysext/core/Tests/Unit/Session/UserSessionTest.php
+++ b/typo3/sysext/core/Tests/Unit/Session/UserSessionTest.php
@@ -17,11 +17,14 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Tests\Unit\Session;
 
+use TYPO3\CMS\Core\Security\JwtTrait;
 use TYPO3\CMS\Core\Session\UserSession;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 class UserSessionTest extends UnitTestCase
 {
+    use JwtTrait;
+
     /**
      * @test
      */
@@ -58,6 +61,7 @@ class UserSessionTest extends UnitTestCase
 
         self::assertTrue($session->dataWasUpdated());
         self::assertEquals(['override' => 'data'], $session->getData());
+        self::assertSame($record['ses_id'], UserSession::resolveIdentifierFromJwt($session->getJwt()));
     }
 
     /**
diff --git a/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php b/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
index 2226dc9bb1ba48061bd5e63ed2cf8ae3924ec5d3..2b9f6342dadf115fc3f039062e32cef67931567f 100644
--- a/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\Security\JwtTrait;
 use TYPO3\CMS\Core\Security\RequestToken;
 use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
 use TYPO3\CMS\Core\Session\UserSession;
@@ -47,6 +48,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 class FrontendUserAuthenticationTest extends UnitTestCase
 {
     use ProphecyTrait;
+    use JwtTrait;
 
     private const NOT_CHECKED_INDICATOR = '--not-checked--';
 
@@ -59,11 +61,19 @@ class FrontendUserAuthenticationTest extends UnitTestCase
      */
     public function userFieldIsNotSetForAnonymousSessions(): void
     {
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = 'secret-encryption-key-test';
         $uniqueSessionId = StringUtility::getUniqueId('test');
+        $uniqueSessionIdJwt = self::encodeHashSignedJwt(
+            [
+                'identifier' => $uniqueSessionId,
+                'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339),
+            ],
+            self::createSigningKeyFromEncryptionKey(UserSession::class)
+        );
 
         // Prepare a request with session id cookie
         $request = new ServerRequest('http://example.com/', 'GET', null, [], []);
-        $request = $request->withCookieParams(['fe_typo_user' => $uniqueSessionId]);
+        $request = $request->withCookieParams(['fe_typo_user' => $uniqueSessionIdJwt]);
 
         // Main session backend setup
         $sessionBackendProphecy = $this->prophesize(SessionBackendInterface::class);
@@ -81,6 +91,7 @@ class FrontendUserAuthenticationTest extends UnitTestCase
             86400,
             new IpLocker(0, 0)
         );
+        $userSessionManager->setLogger(new NullLogger());
         $subject = new FrontendUserAuthentication();
         $subject->setLogger(new NullLogger());
         $subject->initializeUserSessionManager($userSessionManager);