From 63c8ee2d9255b41e350801c663b93073bf20d80d Mon Sep 17 00:00:00 2001 From: Todd Burry Date: Fri, 12 Jun 2020 14:41:04 -0400 Subject: [PATCH] Make sure to issue a new cookie if the old one is valid, but expired --- dist/functions.jsconnect.php | 26 +++++++++++++++++++++++++- src/JsConnect.php | 27 ++++++++++++++++++++++++++- src/JsConnectServer.php | 5 +++++ tests/JsConnectServerTest.php | 11 +++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/dist/functions.jsconnect.php b/dist/functions.jsconnect.php index 3e6d877..a09bb6e 100644 --- a/dist/functions.jsconnect.php +++ b/dist/functions.jsconnect.php @@ -268,6 +268,10 @@ class JsConnect * @var string */ protected $signingAlgorithm; + /** + * @var int + */ + protected $timeout = self::TIMEOUT; /** * JsConnect constructor. */ @@ -468,7 +472,7 @@ public function setGuest(bool $isGuest) */ public function jwtEncode(array $payload) : string { - $payload += ['v' => $this->getVersion(), 'iat' => $this->getTimestamp(), 'exp' => $this->getTimestamp() + self::TIMEOUT]; + $payload += ['v' => $this->getVersion(), 'iat' => $this->getTimestamp(), 'exp' => $this->getTimestamp() + $this->getTimeout()]; $jwt = JWT::encode($payload, $this->getSigningSecret(), $this->getSigningAlgorithm(), null, [self::FIELD_CLIENT_ID => $this->getSigningClientID()]); return $jwt; } @@ -591,6 +595,26 @@ public function getVersion() : string { return self::VERSION; } + /** + * Get the JWT expiry timeout. + * + * @return int + */ + public function getTimeout() : int + { + return $this->timeout; + } + /** + * Set the JWT expiry timeout. + * + * @param int $timeout + * @return $this + */ + public function setTimeout(int $timeout) + { + $this->timeout = $timeout; + return $this; + } } } diff --git a/src/JsConnect.php b/src/JsConnect.php index b921fee..2bebcb3 100644 --- a/src/JsConnect.php +++ b/src/JsConnect.php @@ -64,6 +64,11 @@ class JsConnect { */ protected $signingAlgorithm; + /** + * @var int + */ + protected $timeout = self::TIMEOUT; + /** * JsConnect constructor. */ @@ -278,7 +283,7 @@ public function jwtEncode(array $payload): string { $payload += [ 'v' => $this->getVersion(), 'iat' => $this->getTimestamp(), - 'exp' => $this->getTimestamp() + self::TIMEOUT, + 'exp' => $this->getTimestamp() + $this->getTimeout(), ]; $jwt = JWT::encode($payload, $this->getSigningSecret(), $this->getSigningAlgorithm(), null, [ @@ -405,4 +410,24 @@ final public static function decodeJWTHeader(string $jwt): ?array { public function getVersion(): string { return self::VERSION; } + + /** + * Get the JWT expiry timeout. + * + * @return int + */ + public function getTimeout(): int { + return $this->timeout; + } + + /** + * Set the JWT expiry timeout. + * + * @param int $timeout + * @return $this + */ + public function setTimeout(int $timeout) { + $this->timeout = $timeout; + return $this; + } } diff --git a/src/JsConnectServer.php b/src/JsConnectServer.php index b2dcba3..dc6ad2f 100644 --- a/src/JsConnectServer.php +++ b/src/JsConnectServer.php @@ -7,6 +7,7 @@ namespace Vanilla\JsConnect; +use Firebase\JWT\ExpiredException; use Firebase\JWT\JWT; use Vanilla\JsConnect\Exceptions\InvalidValueException; @@ -46,6 +47,9 @@ public function generateRequest(array $state = []): array { // The cookie was already set. Make sure it's one of ours. try { $decodedCookie = $this->jwtDecode($state[self::FIELD_COOKIE]); + } catch (ExpiredException $ex) { + // The cookie was valid, but expired so issue a new one. + goto GENERATE_COOKIE; } catch (\Exception $ex) { throw new InvalidValueException('Could not use supplied jsConnect SSO token: '.$ex->getMessage()); } @@ -53,6 +57,7 @@ public function generateRequest(array $state = []): array { $cookie = $state[self::FIELD_COOKIE]; unset($state[self::FIELD_COOKIE]); } else { + GENERATE_COOKIE: $nonce = JWT::urlsafeB64Encode(openssl_random_pseudo_bytes(15)); $cookie = $this->jwtEncode([self::FIELD_NONCE => $nonce]); } diff --git a/tests/JsConnectServerTest.php b/tests/JsConnectServerTest.php index fb9b353..3244398 100644 --- a/tests/JsConnectServerTest.php +++ b/tests/JsConnectServerTest.php @@ -123,6 +123,17 @@ public function testCookieReuse(): void { $this->assertArrayNotHasKey(JsConnectServer::FIELD_COOKIE, $decoded2, "The cookie should be double encoded."); } + /** + * If I try to re-use an expired, but valid cookie then a new one should be generated. + */ + public function testCookieReuseTimeout(): void { + $this->jsc->setTimeout(1); + list($_, $cookie) = $this->jsc->generateRequest(); + sleep(2); + list($_, $cookie2) = $this->jsc->generateRequest([JsConnectServer::FIELD_COOKIE => $cookie]); + $this->assertNotSame($cookie, $cookie2); + } + /** * I should not be able to re-use any old cookie value for JsConnect. */