Skip to content

Commit b46fa2f

Browse files
authored
[SDK-2741] Improvements to session/transient cookie storage (#542)
* feat: Improvements to cookie handling * tests: Update tests for new cookie handling * tests: Testing improvements * tests: Testing improvements * Apply feedback: remove join() instances * Fix merge conflicts * Fix merge conflicts * fix: Don't unnecessarily bas64_encode the cookie payload * tests: Fix for static analysis warning * tests: Fix for static analysis warning * tests: Fix merge conflicts * tests: Fix code styling warnings from merge * Implement review feedback from @jimmyjames
1 parent 99338e9 commit b46fa2f

18 files changed

+720
-236
lines changed

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@
2929
],
3030
"require": {
3131
"php": "^7.4 || ^8.0",
32-
"ext-json": "*",
33-
"ext-openssl": "*",
3432
"ext-filter": "*",
33+
"ext-json": "*",
3534
"ext-mbstring": "*",
35+
"ext-openssl": "*",
3636
"php-http/discovery": "^1.0",
3737
"php-http/httplug": "^2.2",
3838
"php-http/multipart-stream-builder": "^1.1",
3939
"psr/cache": "^1.0 || ^2.0 || ^3.0",
4040
"psr/event-dispatcher": "^1.0",
4141
"psr/http-client-implementation": "^1.0",
42-
"psr/http-message-implementation": "^1.0",
43-
"psr/http-factory-implementation": "^1.0"
42+
"psr/http-factory-implementation": "^1.0",
43+
"psr/http-message-implementation": "^1.0"
4444
},
4545
"require-dev": {
4646
"symfony/cache": "^4.4 || ^5.2",

phpinsights.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@
4646
],
4747
\SlevomatCodingStandard\Sniffs\Functions\UnusedParameterSniff::class => [
4848
'exclude' => [
49+
'src/API/Management.php',
4950
'src/Configuration/SdkConfiguration.php',
5051
'src/Configuration/SdkState.php',
51-
'src/API/Management.php',
52+
'src/Store/SessionStore.php',
53+
'src/Store/InMemoryStorage.php',
54+
'src/Store/Psr6Store.php',
5255
],
5356
],
5457
\SlevomatCodingStandard\Sniffs\Classes\ModernClassNameReferenceSniff::class => [

phpstan.neon.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ parameters:
4343
- '#Cannot call method get\(\) on Auth0\\SDK\\Contract\\StoreInterface\|null.#'
4444
- '#Cannot call method set\(\) on Auth0\\SDK\\Contract\\StoreInterface\|null.#'
4545
- '#Cannot call method delete\(\) on Auth0\\SDK\\Contract\\StoreInterface\|null.#'
46+
- '#Cannot call method defer\(\) on Auth0\\SDK\\Contract\\StoreInterface\|null.#'
47+
- '#Cannot call method purge\(\) on Auth0\\SDK\\Contract\\StoreInterface\|null.#'
4648
- '#Instanceof between Auth0\\SDK\\Configuration\\SdkConfiguration and Auth0\\SDK\\Configuration\\SdkConfiguration will always evaluate to true.#'
4749
- '#caught "Throwable" must be rethrown.#'
4850
- '#Class OpenSSLAsymmetricKey not found.#'

src/Auth0.php

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,20 @@ public function logout(
176176
*/
177177
public function clear(): self
178178
{
179-
$sessionStorage = $this->configuration()->getSessionStorage();
180-
$transientStorage = $this->configuration()->getTransientStorage();
181-
182-
if ($sessionStorage !== null) {
183-
$sessionStorage->deleteAll();
179+
// Delete all data in the session storage medium.
180+
if ($this->configuration()->hasSessionStorage()) {
181+
$this->configuration->getSessionStorage()->purge();
184182
}
185183

186-
if ($transientStorage !== null) {
187-
$transientStorage->deleteAll();
184+
// Delete all data in the transient storage medium.
185+
if ($this->configuration()->hasTransientStorage()) {
186+
$this->configuration->getTransientStorage()->purge();
188187
}
189188

189+
// If state saving had been deferred, disable it and force a update to persistent storage.
190+
$this->deferStateSaving(false);
191+
192+
// Reset the internal state.
190193
$this->getState()->reset();
191194

192195
return $this;
@@ -259,6 +262,8 @@ public function decode(
259262
public function exchange(
260263
?string $redirectUri = null
261264
): bool {
265+
$this->deferStateSaving();
266+
262267
$code = $this->getRequestParameter('code');
263268
$state = $this->getRequestParameter('state');
264269
$codeVerifier = null;
@@ -277,6 +282,7 @@ public function exchange(
277282
$codeVerifier = $this->getTransientStore()->getOnce('code_verifier');
278283

279284
if ($codeVerifier === null) {
285+
$this->clear();
280286
throw \Auth0\SDK\Exception\StateException::missingCodeVerifier();
281287
}
282288
}
@@ -333,6 +339,8 @@ public function exchange(
333339
}
334340

335341
$this->setUser($user ?? []);
342+
$this->deferStateSaving(false);
343+
336344
return true;
337345
}
338346

@@ -352,16 +360,19 @@ public function exchange(
352360
public function renew(
353361
?array $params = null
354362
): self {
363+
$this->deferStateSaving();
355364
$refreshToken = $this->getState()->getRefreshToken();
356365

357366
if ($refreshToken === null) {
367+
$this->clear();
358368
throw \Auth0\SDK\Exception\StateException::failedRenewTokenMissingRefreshToken();
359369
}
360370

361371
$response = $this->authentication()->refreshToken($refreshToken, $params);
362372
$response = HttpResponse::decodeContent($response);
363373

364374
if (! isset($response['access_token']) || ! $response['access_token']) {
375+
$this->clear();
365376
throw \Auth0\SDK\Exception\StateException::failedRenewTokenMissingAccessToken();
366377
}
367378

@@ -371,6 +382,8 @@ public function renew(
371382
$this->setIdToken($response['id_token']);
372383
}
373384

385+
$this->deferStateSaving(false);
386+
374387
return $this;
375388
}
376389

@@ -709,4 +722,24 @@ private function getState(): SdkState
709722

710723
return $this->state;
711724
}
725+
726+
/**
727+
* Defer saving transient or session states to destination medium.
728+
* Improves performance during large blocks of changes.
729+
*
730+
* @param bool $deferring Whether to defer persisting the storage state.
731+
*/
732+
private function deferStateSaving(
733+
bool $deferring = true
734+
): self {
735+
if ($this->configuration()->hasSessionStorage()) {
736+
$this->configuration()->getSessionStorage()->defer($deferring);
737+
}
738+
739+
if ($this->configuration()->hasTransientStorage()) {
740+
$this->configuration()->getTransientStorage()->defer($deferring);
741+
}
742+
743+
return $this;
744+
}
712745
}

src/Configuration/SdkConfiguration.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
* @method SdkConfiguration setResponseType(string $responseType = 'code')
5353
* @method SdkConfiguration setScope(?array $scope = null)
5454
* @method SdkConfiguration setSessionStorage(?StoreInterface $sessionStorage = null)
55+
* @method SdkConfiguration setSessionStorageId(string $sessionStorageId = 'auth0_session')
5556
* @method SdkConfiguration setStrategy(string $strategy = 'webapp')
5657
* @method SdkConfiguration setTokenAlgorithm(string $tokenAlgorithm = 'RS256')
5758
* @method SdkConfiguration setTokenCache(?CacheItemPoolInterface $cache = null)
@@ -60,6 +61,7 @@
6061
* @method SdkConfiguration setTokenLeeway(int $tokenLeeway = 60)
6162
* @method SdkConfiguration setTokenMaxAge(?int $tokenMaxAge = null)
6263
* @method SdkConfiguration setTransientStorage(?StoreInterface $transientStorage = null)
64+
* @method SdkConfiguration setTransientStorageId(string $transientStorageId = 'auth0_transient')
6365
* @method SdkConfiguration setUsePkce(bool $usePkce)
6466
*
6567
* @method array<string>|null getAudience(?\Throwable $exceptionIfNull = null)
@@ -91,6 +93,7 @@
9193
* @method string getResponseType()
9294
* @method array<string> getScope()
9395
* @method StoreInterface|null getSessionStorage(?\Throwable $exceptionIfNull = null)
96+
* @method string getSessionStorageId()
9497
* @method string getStrategy()
9598
* @method string getTokenAlgorithm()
9699
* @method CacheItemPoolInterface|null getTokenCache(?\Throwable $exceptionIfNull = null)
@@ -99,6 +102,7 @@
99102
* @method int|null getTokenLeeway(?\Throwable $exceptionIfNull = null)
100103
* @method int|null getTokenMaxAge(?\Throwable $exceptionIfNull = null)
101104
* @method StoreInterface|null getTransientStorage(?\Throwable $exceptionIfNull = null)
105+
* @method string getTransientStorageId()
102106
* @method bool getUsePkce()
103107
*
104108
* @method bool hasAudience()
@@ -130,13 +134,15 @@
130134
* @method bool hasResponseType()
131135
* @method bool hasScope()
132136
* @method bool hasSessionStorage()
137+
* @method bool hasSessionStorageId()
133138
* @method bool hasStrategy()
134139
* @method bool hasTokenAlgorithm()
135140
* @method bool hasTokenCache()
136141
* @method bool hasTokenCacheTtl()
137142
* @method bool hasTokenLeeway()
138143
* @method bool hasTokenMaxAge()
139144
* @method bool hasTransientStorage()
145+
* @method bool hasTransientStorageId()
140146
* @method bool hasUsePkce()
141147
*
142148
* @method bool pushScope($scope)
@@ -180,6 +186,7 @@ final class SdkConfiguration implements ConfigurableContract
180186
* @param StreamFactoryInterface|null $httpStreamFactory A PSR-17 compatible stream factory to create request body streams.
181187
* @param bool $httpTelemetry Defaults to true. If true, API requests will include telemetry about the SDK and PHP runtime version to help us improve our services.
182188
* @param StoreInterface|null $sessionStorage Defaults to use cookies. A StoreInterface-compatible class for storing Token state.
189+
* @param string $sessionStorageId Defaults to 'auth0_session'. The namespace to prefix session items under.
183190
* @param string|null $cookieSecret The secret used to derive an encryption key for the user identity in a session cookie and to sign the transient cookies used by the login callback.
184191
* @param string|null $cookieDomain Defaults to value of HTTP_HOST server environment information. Cookie domain, for example 'www.example.com', for use with PHP sessions and SDK cookies. To make cookies visible on all subdomains then the domain must be prefixed with a dot like '.example.com'.
185192
* @param int $cookieExpires Defaults to 0. How long, in seconds, before cookies expire. If set to 0 the cookie will expire at the end of the session (when the browser closes).
@@ -190,6 +197,7 @@ final class SdkConfiguration implements ConfigurableContract
190197
* @param bool $persistAccessToken Defaults to true. If true, the Access Token will persist in session storage.
191198
* @param bool $persistRefreshToken Defaults to true. If true, the Refresh Token will persist in session storage.
192199
* @param StoreInterface|null $transientStorage Defaults to use cookies. A StoreInterface-compatible class for storing ephemeral state data, such as nonces.
200+
* @param string $transientStorageId Defaults to 'auth0_transient'. The namespace to prefix transient items under.
193201
* @param bool $queryUserInfo Defaults to false. If true, query the /userinfo endpoint during an authorization code exchange.
194202
* @param string|null $managementToken An Access Token to use for Management API calls. If there isn't one specified, the SDK will attempt to get one for you using your $clientSecret.
195203
* @param CacheItemPoolInterface|null $managementTokenCache A PSR-6 compatible cache adapter for storing generated management access tokens.
@@ -223,6 +231,7 @@ public function __construct(
223231
?StreamFactoryInterface $httpStreamFactory = null,
224232
bool $httpTelemetry = true,
225233
?StoreInterface $sessionStorage = null,
234+
string $sessionStorageId = 'auth0_session',
226235
?string $cookieSecret = null,
227236
?string $cookieDomain = null,
228237
int $cookieExpires = 0,
@@ -233,6 +242,7 @@ public function __construct(
233242
bool $persistAccessToken = true,
234243
bool $persistRefreshToken = true,
235244
?StoreInterface $transientStorage = null,
245+
string $transientStorageId = 'auth0_transient',
236246
bool $queryUserInfo = false,
237247
?string $managementToken = null,
238248
?CacheItemPoolInterface $managementTokenCache = null,
@@ -389,11 +399,11 @@ private function setupStateFactories(): void
389399
private function setupStateStorage(): void
390400
{
391401
if (! $this->getSessionStorage() instanceof StoreInterface) {
392-
$this->setSessionStorage(new CookieStore($this, 'auth0_session'));
402+
$this->setSessionStorage(new CookieStore($this, $this->getSessionStorageId()));
393403
}
394404

395405
if (! $this->getTransientStorage() instanceof StoreInterface) {
396-
$this->setTransientStorage(new CookieStore($this, 'auth0_transient'));
406+
$this->setTransientStorage(new CookieStore($this, $this->getTransientStorageId()));
397407
}
398408
}
399409

src/Contract/StoreInterface.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public function set(
2323
/**
2424
* Get a value from the store by a given key.
2525
*
26-
* @param string $key Key to get.
27-
* @param string|null $default Return value if key not found.
26+
* @param string $key Key to get.
27+
* @param mixed $default Return value if key not found.
2828
*
2929
* @return mixed
3030
*/
3131
public function get(
3232
string $key,
33-
?string $default = null
33+
$default = null
3434
);
3535

3636
/**
@@ -45,5 +45,12 @@ public function delete(
4545
/**
4646
* Remove all stored values
4747
*/
48-
public function deleteAll(): void;
48+
public function purge(): void;
49+
50+
/**
51+
* Defer saving state changes to destination to improve performance during blocks of changes.
52+
*/
53+
public function defer(
54+
bool $deferring
55+
): void;
4956
}

0 commit comments

Comments
 (0)