Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

- [Patch] Fix `Session::isValid()` to use inclusion-based scope validation so sessions with all configured scopes plus additional granted scopes are not incorrectly rejected. Aligns PHP SDK behaviour with the Node SDK.

## v6.1.1 - 2026-03-02
- [#456](https://github.com/Shopify/shopify-api-php/pull/456) [Patch] Update firebase/php-jwt to ^7.0 to address security vulnerability (GHSA-2x45-7fc3-mxwq)

Expand Down
9 changes: 8 additions & 1 deletion src/Auth/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,18 @@ public function clone(string $newSessionId): Session
/**
* Checks whether this session has all of the necessary settings to make requests to Shopify.
*
* A session is considered valid when its granted scopes include all of the app's configured
* scopes (the session may have additional granted scopes beyond those required), it has an
* access token, and it has not expired. This inclusion-based check aligns with the Node SDK's
* scope validation behaviour and avoids rejecting valid sessions that were granted optional or
* additional scopes beyond the configured set.
*
* @return bool
*/
public function isValid(): bool
{
return (Context::$SCOPES->equals($this->scope) &&
$sessionScopes = new Scopes($this->scope ?? []);
return ($sessionScopes->has(Context::$SCOPES) &&
$this->accessToken &&
(!$this->expires || ($this->expires > new DateTime()))
);
Expand Down
17 changes: 16 additions & 1 deletion tests/Auth/SessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,22 @@ public function testIsValidReturnsTrue()
$this->assertTrue($session->isValid());
}

public function testIsValidReturnsFalseIfScopesHaveChanged()
public function testIsValidReturnsTrueWhenSessionHasAdditionalGrantedScopes()
{
// Sessions with all configured scopes PLUS extra granted scopes must still be valid.
// This mirrors the Node SDK's inclusion-based scope check and prevents
// Utils::loadOfflineSession() from returning null for sessions that include optional scopes.
Context::$SCOPES = new Scopes('read_products');

$session = new Session('12345', 'my-shop.myshopify.io', true, '1234');
$session->setScope('read_products,write_orders');
$session->setExpires(strtotime('+10 minutes'));
$session->setAccessToken('totally_real_token');

$this->assertTrue($session->isValid());
}

public function testIsValidReturnsFalseIfScopesAreMissing()
{
Context::$SCOPES = new Scopes('read_products,write_orders');

Expand Down
Loading