Shopware has Improper Session Handling in store-api
Description
Shopware 6 is an open commerce platform based on Symfony Framework and Vue. Starting in version 6.3.5.0 and prior to versions 6.6.1.0 and 6.5.8.8, when a authenticated request is made to POST /store-api/account/logout, the cart will be cleared, but the User won't be logged out. This affects only the direct store-api usage, as the PHP Storefront listens additionally on CustomerLogoutEvent and invalidates the session additionally. The problem has been fixed in Shopware 6.6.1.0 and 6.5.8.8. Those who are unable to update can install the latest version of the Shopware Security Plugin as a workaround.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
shopware/corePackagist | >= 6.3.5.0, < 6.5.8.8 | 6.5.8.8 |
shopware/platformPackagist | >= 6.3.5.0, < 6.5.8.8 | 6.5.8.8 |
shopware/corePackagist | >= 6.6.0.0-rc1, < 6.6.1.0 | 6.6.1.0 |
shopware/platformPackagist | >= 6.6.0.0-rc1, < 6.6.1.0 | 6.6.1.0 |
Affected products
1Patches
25cc84ddd817aNEXT-34608 - Improve account logout
4 files changed · +13 −5
changelog/_unreleased/2024-04-03-improve-account-logout.md+8 −0 added@@ -0,0 +1,8 @@ +--- +title: Improve account logout +issue: NEXT-34608 +--- + +# Core + +* Changed `LogoutRoute` to always logout the user regardless of the configurations.
src/Core/Checkout/Customer/SalesChannel/LogoutRoute.php+2 −0 modified@@ -39,6 +39,8 @@ public function getDecorated(): AbstractLogoutRoute #[Route(path: '/store-api/account/logout', name: 'store-api.account.logout', methods: ['POST'], defaults: ['_loginRequired' => true, '_loginRequiredAllowGuest' => true])] public function logout(SalesChannelContext $context, RequestDataBag $data): ContextTokenResponse { + $this->contextPersister->save($context->getToken(), ['customerId' => null], $context->getSalesChannelId()); + /** @var CustomerEntity $customer */ $customer = $context->getCustomer(); if ($this->shouldDelete($context)) {
tests/integration/Core/Checkout/Customer/SalesChannel/LogoutRouteTest.php+2 −4 modified@@ -166,7 +166,7 @@ public function testLoggedOutUpdateCustomerContextWithReplaceTokenParameter(): v $newCustomerContextToken = $this->getContainer()->get(Connection::class)->fetchOne('SELECT token FROM sales_channel_api_context WHERE customer_id = ?', [$currentCustomerId]); - static::assertNotEmpty($newCustomerContextToken); + static::assertEmpty($newCustomerContextToken); static::assertNotEquals($currentCustomerToken, $newCustomerContextToken); } @@ -193,7 +193,6 @@ public function testLoggedOutKeepCustomerContextWithoutReplaceTokenParameter(): $response = $this->browser->getResponse(); $currentCustomerToken = $response->headers->get(PlatformRequest::HEADER_CONTEXT_TOKEN) ?: ''; - $currentCustomerId = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); $this->browser->setServerParameter('HTTP_SW_CONTEXT_TOKEN', $currentCustomerToken); @@ -208,8 +207,7 @@ public function testLoggedOutKeepCustomerContextWithoutReplaceTokenParameter(): ); $customerIdWithOldToken = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); - - static::assertEquals($currentCustomerId, $customerIdWithOldToken); + static::assertEmpty($customerIdWithOldToken); } public function testLogoutRouteReturnContextTokenResponse(): void
tests/integration/Storefront/Controller/AuthControllerTest.php+1 −1 modified@@ -262,7 +262,7 @@ public function testOneUserUseOneContextAcrossSessions(): void $secondTimeLoginContextToken = $secondTimeLogin->get(PlatformRequest::HEADER_CONTEXT_TOKEN); static::assertNotEquals($firstTimeLoginSessionId, $secondTimeLoginSessionId); - static::assertEquals($firstTimeLoginContextToken, $secondTimeLoginContextToken); + static::assertNotEquals($firstTimeLoginContextToken, $secondTimeLoginContextToken); } public function testMergedHintIsAdded(): void
d29775aa758fNEXT-34608 - Improve account logout
3 files changed · +10 −92
src/Core/Checkout/Customer/SalesChannel/LogoutRoute.php+5 −10 modified@@ -36,23 +36,18 @@ public function getDecorated(): AbstractLogoutRoute throw new DecorationPatternException(self::class); } - #[Route(path: '/store-api/account/logout', name: 'store-api.account.logout', methods: ['POST'], defaults: ['_loginRequired' => true, '_loginRequiredAllowGuest' => true])] + #[Route(path: '/store-api/account/logout', name: 'store-api.account.logout', defaults: ['_loginRequired' => true, '_loginRequiredAllowGuest' => true], methods: ['POST'])] public function logout(SalesChannelContext $context, RequestDataBag $data): ContextTokenResponse { + $newToken = Random::getAlphanumericString(32); + /** @var CustomerEntity $customer */ $customer = $context->getCustomer(); if ($this->shouldDelete($context)) { $this->cartService->deleteCart($context); $this->contextPersister->delete($context->getToken(), $context->getSalesChannelId()); - - $event = new CustomerLogoutEvent($context, $customer); - $this->eventDispatcher->dispatch($event); - - return new ContextTokenResponse($context->getToken()); - } - - $newToken = Random::getAlphanumericString(32); - if ((bool) $data->get('replace-token')) { + } else { + $this->contextPersister->save($context->getToken(), ['customerId' => null], $context->getSalesChannelId()); $newToken = $this->contextPersister->replace($context->getToken(), $context); }
tests/integration/Core/Checkout/Customer/SalesChannel/LogoutRouteTest.php+4 −81 modified@@ -55,8 +55,6 @@ public function testNotLoggedin(): void ->request( 'POST', '/store-api/account/logout', - [ - ] ); static::assertIsString($this->browser->getResponse()->getContent()); @@ -95,24 +93,14 @@ public function testValidLogout(): void ->request( 'POST', '/store-api/account/logout', - [ - 'replace-token' => true, - ], - [], - [ - ] ); static::assertSame(200, $this->browser->getResponse()->getStatusCode()); $this->browser ->request( 'POST', - '/store-api/account/customer', - [], - [], - [ - ] + '/store-api/account/customer' ); static::assertIsString($this->browser->getResponse()->getContent()); @@ -121,55 +109,6 @@ public function testValidLogout(): void static::assertArrayHasKey('errors', $response); } - public function testLoggedOutUpdateCustomerContextWithReplaceTokenParameter(): void - { - $systemConfig = $this->getContainer()->get(SystemConfigService::class); - $systemConfig->set('core.loginRegistration.invalidateSessionOnLogOut', false); - - $email = Uuid::randomHex() . '@example.com'; - $this->createCustomer($email); - - $this->browser - ->request( - 'POST', - '/store-api/account/login', - [ - 'email' => $email, - 'password' => 'shopware', - ] - ); - - static::assertIsString($this->browser->getResponse()->getContent()); - - $response = $this->browser->getResponse(); - - $currentCustomerToken = $response->headers->get(PlatformRequest::HEADER_CONTEXT_TOKEN) ?: ''; - $currentCustomerId = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); - - $this->browser->setServerParameter('HTTP_SW_CONTEXT_TOKEN', $currentCustomerToken); - - $this->browser - ->request( - 'POST', - '/store-api/account/logout', - [ - 'replace-token' => true, - ], - [], - [ - ] - ); - - $customerIdWithOldToken = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); - - static::assertFalse($customerIdWithOldToken); - - $newCustomerContextToken = $this->getContainer()->get(Connection::class)->fetchOne('SELECT token FROM sales_channel_api_context WHERE customer_id = ?', [$currentCustomerId]); - - static::assertNotEmpty($newCustomerContextToken); - static::assertNotEquals($currentCustomerToken, $newCustomerContextToken); - } - public function testLoggedOutKeepCustomerContextWithoutReplaceTokenParameter(): void { $systemConfig = $this->getContainer()->get(SystemConfigService::class); @@ -193,23 +132,17 @@ public function testLoggedOutKeepCustomerContextWithoutReplaceTokenParameter(): $response = $this->browser->getResponse(); $currentCustomerToken = $response->headers->get(PlatformRequest::HEADER_CONTEXT_TOKEN) ?: ''; - $currentCustomerId = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); $this->browser->setServerParameter('HTTP_SW_CONTEXT_TOKEN', $currentCustomerToken); $this->browser ->request( 'POST', '/store-api/account/logout', - [], - [], - [ - ] ); $customerIdWithOldToken = $this->getContainer()->get(Connection::class)->fetchOne('SELECT customer_id FROM sales_channel_api_context WHERE token = ?', [$currentCustomerToken]); - - static::assertEquals($currentCustomerId, $customerIdWithOldToken); + static::assertFalse($customerIdWithOldToken, 'The old token should be gone'); } public function testLogoutRouteReturnContextTokenResponse(): void @@ -286,7 +219,7 @@ public function testLogoutForcedForGuestAccounts(): void ->logout($context, $request); static::assertInstanceOf(ContextTokenResponse::class, $logout); - static::assertEquals($login->getToken(), $logout->getToken()); + static::assertNotEquals($login->getToken(), $logout->getToken()); $exists = $this->getContainer()->get(Connection::class) ->fetchAllAssociative('SELECT * FROM sales_channel_api_context WHERE token = :token', ['token' => $login->getToken()]); @@ -307,12 +240,6 @@ public function testValidLogoutAsGuest(): void ->request( 'POST', '/store-api/account/logout', - [ - 'replace-token' => true, - ], - [], - [ - ] ); static::assertIsString($this->browser->getResponse()->getContent()); @@ -325,11 +252,7 @@ public function testValidLogoutAsGuest(): void $this->browser ->request( 'POST', - '/store-api/account/customer', - [], - [], - [ - ] + '/store-api/account/customer' ); static::assertIsString($this->browser->getResponse()->getContent());
tests/integration/Storefront/Controller/AuthControllerTest.php+1 −1 modified@@ -262,7 +262,7 @@ public function testOneUserUseOneContextAcrossSessions(): void $secondTimeLoginContextToken = $secondTimeLogin->get(PlatformRequest::HEADER_CONTEXT_TOKEN); static::assertNotEquals($firstTimeLoginSessionId, $secondTimeLoginSessionId); - static::assertEquals($firstTimeLoginContextToken, $secondTimeLoginContextToken); + static::assertNotEquals($firstTimeLoginContextToken, $secondTimeLoginContextToken); } public function testMergedHintIsAdded(): void
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-5297-wrrp-rcj7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-31447ghsaADVISORY
- github.com/shopware/shopware/commit/5cc84ddd817ad0c1d07f9b3c79ab346d50514a77ghsax_refsource_MISCWEB
- github.com/shopware/shopware/commit/d29775aa758f70d08e0c5999795c7c26d230e7d3ghsax_refsource_MISCWEB
- github.com/shopware/shopware/security/advisories/GHSA-5297-wrrp-rcj7ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.