diff --git a/typo3/sysext/core/Classes/Http/CookieScopeTrait.php b/typo3/sysext/core/Classes/Http/CookieScopeTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..e5ddad67bc1e5b22d3db17f8a2eaea9d57bf46e8
--- /dev/null
+++ b/typo3/sysext/core/Classes/Http/CookieScopeTrait.php
@@ -0,0 +1,67 @@
+<?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\Http;
+
+trait CookieScopeTrait
+{
+    /**
+     * Returns the domain and path to be used for setting cookies.
+     * The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] if set,
+     * otherwise the normalized request params are used.
+     *
+     * @return array{domain: string, path: string} The domain and path to be used when setting cookies
+     */
+    private function getCookieScope(NormalizedParams $normalizedParams): array
+    {
+        $cookieDomain = $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'] ?? '';
+        // If a specific cookie domain is defined for a given application type, use that domain
+        if (!empty($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'])) {
+            $cookieDomain = $GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieDomain'];
+        }
+        if (!$cookieDomain) {
+            return [
+                'domain' => $normalizedParams->getRequestHostOnly(),
+                // If no cookie domain is set, use the base path
+                'path' => $normalizedParams->getSitePath(),
+            ];
+        }
+        if ($cookieDomain[0] === '/') {
+            $match = [];
+            $matchCount = @preg_match($cookieDomain, $normalizedParams->getRequestHostOnly(), $match);
+            if ($matchCount === false) {
+                $this->logger->critical(
+                    'The regular expression for the cookie domain ({domain}) contains errors. The session is not shared across sub-domains.',
+                    ['domain' => $cookieDomain]
+                );
+            }
+            if ($matchCount === false || $matchCount === 0) {
+                return [
+                    'domain' => $normalizedParams->getRequestHostOnly(),
+                    // If no cookie domain could be matched, use the base path
+                    'path' => $normalizedParams->getSitePath(),
+                ];
+            }
+            $cookieDomain = $match[0];
+        }
+
+        return [
+            'domain' => trim($cookieDomain, '.'),
+            'path' => '/',
+        ];
+    }
+}
diff --git a/typo3/sysext/core/Classes/Session/UserSessionManager.php b/typo3/sysext/core/Classes/Session/UserSessionManager.php
index d4a60410f400e8d931be020261233f98ea5bd17a..cb69de751cf617cc771bdb1eea8c3d3eb07ffba6 100644
--- a/typo3/sysext/core/Classes/Session/UserSessionManager.php
+++ b/typo3/sysext/core/Classes/Session/UserSessionManager.php
@@ -21,7 +21,10 @@ use Psr\Http\Message\ServerRequestInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Authentication\IpLocker;
+use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\Random;
+use TYPO3\CMS\Core\Http\CookieScopeTrait;
+use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
 use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -34,6 +37,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class UserSessionManager implements LoggerAwareInterface
 {
     use LoggerAwareTrait;
+    use CookieScopeTrait;
 
     protected const SESSION_ID_LENGTH = 32;
     protected const GARBAGE_COLLECTION_LIFETIME = 86400;
@@ -52,6 +56,7 @@ class UserSessionManager implements LoggerAwareInterface
     protected int $garbageCollectionForAnonymousSessions = self::LIFETIME_OF_ANONYMOUS_SESSION_DATA;
     protected SessionBackendInterface $sessionBackend;
     protected IpLocker $ipLocker;
+    protected string $loginType;
 
     /**
      * Constructor. Marked as internal, as it is recommended to use the factory method "create"
@@ -61,11 +66,12 @@ class UserSessionManager implements LoggerAwareInterface
      * @param IpLocker $ipLocker
      * @internal
      */
-    public function __construct(SessionBackendInterface $sessionBackend, int $sessionLifetime, IpLocker $ipLocker)
+    public function __construct(SessionBackendInterface $sessionBackend, int $sessionLifetime, IpLocker $ipLocker, string $loginType)
     {
         $this->sessionBackend = $sessionBackend;
         $this->sessionLifetime = $sessionLifetime;
         $this->ipLocker = $ipLocker;
+        $this->loginType = $loginType;
     }
 
     protected function setGarbageCollectionTimeoutForAnonymousSessions(int $garbageCollectionForAnonymousSessions = 0): void
@@ -285,13 +291,38 @@ class UserSessionManager implements LoggerAwareInterface
     }
 
     /**
-     * Creates a new session ID using a random with SESSION_ID_LENGTH as length
+     * Creates a new session ID using a random with SESSION_ID_LENGTH as length of the random part
      *
      * @return string
      */
     protected function createSessionId(): string
     {
-        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(self::SESSION_ID_LENGTH);
+        $normalizedParams = $this->getNormalizedParams();
+        $cookieScope = $this->getCookieScope($normalizedParams);
+        $key = sha1($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '/' . UserSession::class . '/' . $cookieScope['domain']);
+        $random = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(self::SESSION_ID_LENGTH);
+        $signature = hash_hmac('sha256', $random, $key);
+
+        return $random . '.' . $signature;
+    }
+
+    /**
+     * @todo/notes for backports: Same as in typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php,
+     */
+    protected function getNormalizedParams(): NormalizedParams
+    {
+        $normalizedParams = null;
+        $serverParams = Environment::isCli() ? ['HTTP_HOST' => 'localhost'] : $_SERVER;
+        if (isset($GLOBALS['TYPO3_REQUEST'])) {
+            $normalizedParams = $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams');
+            $serverParams = $GLOBALS['TYPO3_REQUEST']->getServerParams();
+        }
+
+        if (!$normalizedParams instanceof NormalizedParams) {
+            $normalizedParams = NormalizedParams::createFromServerParams($serverParams);
+        }
+
+        return $normalizedParams;
     }
 
     /**
@@ -306,6 +337,25 @@ class UserSessionManager implements LoggerAwareInterface
         if ($id === '') {
             return null;
         }
+
+        $sessionsParts = explode('.', $id, 2);
+        // Verify if session id is signed with cookie domain.
+        // Note that we allow possibly unsiged session IDs (used for testing framework or 3rd party authenticators)
+        if (count($sessionsParts) === 2) {
+            $random = $sessionsParts[0];
+            $signature = $sessionsParts[1];
+            $normalizedParams = $this->getNormalizedParams();
+            $cookieScope = $this->getCookieScope($normalizedParams);
+            $key = sha1($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '/' . UserSession::class . '/' . $cookieScope['domain']);
+            $validHash = hash_hmac('sha256', $random, $key);
+            if (!hash_equals($validHash, $signature)) {
+                $this->logger->notice('User Session rejected because of invalid signature', ['session' => substr(sha1($id), 0, 12)]);
+                return null;
+            }
+        } elseif ($this->logger !== null) {
+            $this->logger->notice('Unsigned session id has been used', ['session' => substr(sha1($id), 0, 12)]);
+        }
+
         try {
             $sessionRecord = $this->sessionBackend->get($id);
             if ($sessionRecord === []) {
@@ -357,7 +407,8 @@ class UserSessionManager implements LoggerAwareInterface
             self::class,
             $sessionManager->getSessionBackend($loginType),
             $sessionLifetime,
-            $ipLocker
+            $ipLocker,
+            $loginType
         );
         if ($loginType === 'FE') {
             $object->setGarbageCollectionTimeoutForAnonymousSessions((int)($GLOBALS['TYPO3_CONF_VARS']['FE']['sessionDataLifetime'] ?? 0));
diff --git a/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php b/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php
index 293793a625af58c36499addf4615ea27f5418b86..6bcd200bd45776b187f295d37da2af85676fa856 100644
--- a/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php
+++ b/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php
@@ -341,7 +341,8 @@ class PageRendererTest extends FunctionalTestCase
         $userSessionManager = new UserSessionManager(
             $sessionBackend->reveal(),
             86400,
-            $this->prophesize(IpLocker::class)->reveal()
+            $this->prophesize(IpLocker::class)->reveal(),
+            'BE'
         );
         $GLOBALS['BE_USER'] = new BackendUserAuthentication();
         $GLOBALS['BE_USER']->initializeUserSessionManager($userSessionManager);
diff --git a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
index 488eb9b2d3c8ca105f1e7cf1e40c03b43df91bd3..83a039e683e38347603cce927147fcca28fb0848 100644
--- a/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
@@ -101,7 +101,8 @@ class BackendUserAuthenticationTest extends UnitTestCase
         $userSessionManager = new UserSessionManager(
             $sessionBackend->reveal(),
             86400,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'BE'
         );
 
         $GLOBALS['BE_USER'] = $this->getMockBuilder(BackendUserAuthentication::class)->getMock();
diff --git a/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php b/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
index 9a66feb644aef46d1cc1f269f224b7fd956146ea..eaa8c6ceac6167e7f806c97ff90b607a925736d0 100644
--- a/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
+++ b/typo3/sysext/core/Tests/Unit/Session/UserSessionManagerTest.php
@@ -21,6 +21,7 @@ use Prophecy\Argument;
 use Prophecy\PhpUnit\ProphecyTrait;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Authentication\IpLocker;
+use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
 use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface;
 use TYPO3\CMS\Core\Session\UserSession;
@@ -62,7 +63,8 @@ class UserSessionManagerTest extends UnitTestCase
         $subject = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             $sessionLifetime,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $session = $subject->createAnonymousSession();
         self::assertEquals($expectedResult, $subject->willExpire($session, $gracePeriod));
@@ -75,7 +77,8 @@ class UserSessionManagerTest extends UnitTestCase
         $subject = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             60,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $expiredSession = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
         self::assertTrue($subject->hasExpired($expiredSession));
@@ -88,9 +91,16 @@ class UserSessionManagerTest extends UnitTestCase
      */
     public function createFromRequestOrAnonymousCreatesProperSessionObjects(): void
     {
+        $cookieDomain = 'example.org';
+        $normalizedParams = $this->createMock(NormalizedParams::class);
+        $normalizedParams->method('getRequestHostOnly')->willReturn($cookieDomain);
+        $key = sha1($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] . '/' . UserSession::class . '/' . $cookieDomain);
+        $sessionId = 'valid-session';
+        $signature = hash_hmac('sha256', $sessionId, $key);
+        $validSession = $sessionId . '.' . $signature;
         $sessionBackendProphecy = $this->prophesize(SessionBackendInterface::class);
         $sessionBackendProphecy->get('invalid-session')->willThrow(SessionNotFoundException::class);
-        $sessionBackendProphecy->get('valid-session')->willReturn([
+        $sessionBackendProphecy->get($validSession)->willReturn([
             'ses_id' => 'valid-session',
             'ses_userid' => 13,
             'ses_data' => serialize(['propertyA' => 42, 'propertyB' => 'great']),
@@ -100,18 +110,24 @@ class UserSessionManagerTest extends UnitTestCase
         $subject = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             50,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $request = $this->prophesize(ServerRequestInterface::class);
         $request->getCookieParams()->willReturn([]);
+        $request->getServerParams()->willReturn(['HTTP_HOST' => $cookieDomain]);
+        $request->getAttribute('normalizedParams')->willReturn($normalizedParams);
+        $GLOBALS['TYPO3_REQUEST'] = $request->reveal();
         $anonymousSession = $subject->createFromRequestOrAnonymous($request->reveal(), 'foo');
         self::assertTrue($anonymousSession->isNew());
         self::assertTrue($anonymousSession->isAnonymous());
-        $request->getCookieParams()->willReturn(['foo' => 'invalid-session', 'bar' => 'valid-session']);
+
+        $request->getCookieParams()->willReturn(['foo' => 'invalid-session', 'bar' => $validSession]);
         $anonymousSessionFromInvalidBackendRequest = $subject->createFromRequestOrAnonymous($request->reveal(), 'foo');
         self::assertTrue($anonymousSessionFromInvalidBackendRequest->isNew());
         self::assertTrue($anonymousSessionFromInvalidBackendRequest->isAnonymous());
         $persistedSession = $subject->createFromRequestOrAnonymous($request->reveal(), 'bar');
+
         self::assertEquals(13, $persistedSession->getUserId());
         self::assertFalse($persistedSession->isAnonymous());
         self::assertFalse($persistedSession->isNew());
@@ -136,7 +152,8 @@ class UserSessionManagerTest extends UnitTestCase
         $subject = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             60,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $session = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
         $session = $subject->updateSession($session);
@@ -159,7 +176,8 @@ class UserSessionManagerTest extends UnitTestCase
         $subject = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             60,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $session = UserSession::createFromRecord('random-string', ['ses_tstamp' => time() - 500]);
         $session = $subject->fixateAnonymousSession($session);
diff --git a/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php b/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
index 5ab87b28e09a7a428594b5f3eca10f7ac43c1e1d..3e232765bb28a15322f346e1fb8e544b24b68982 100644
--- a/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Authentication/FrontendUserAuthenticationTest.php
@@ -92,7 +92,8 @@ class FrontendUserAuthenticationTest extends UnitTestCase
         $userSessionManager = new UserSessionManager(
             $sessionBackendProphecy->reveal(),
             86400,
-            new IpLocker(0, 0)
+            new IpLocker(0, 0),
+            'FE'
         );
         $subject = new FrontendUserAuthentication();
         $subject->setLogger(new NullLogger());