diff --git a/src/JsConnectServer.php b/src/JsConnectServer.php index 7ff7c5e..b2dcba3 100644 --- a/src/JsConnectServer.php +++ b/src/JsConnectServer.php @@ -12,6 +12,7 @@ class JsConnectServer extends JsConnect { const FIELD_NONCE = 'n'; + const FIELD_COOKIE = 'cookie'; /** * @var string @@ -41,13 +42,25 @@ public function setKeyStore(\ArrayAccess $keystore) { * @return array Returns an array in the format `[$requestUrl, $cookie]`. */ public function generateRequest(array $state = []): array { - $nonce = JWT::urlsafeB64Encode(openssl_random_pseudo_bytes(15)); - $cookie = $this->jwtEncode([self::FIELD_NONCE => $nonce]); + if (isset($state[self::FIELD_COOKIE])) { + // The cookie was already set. Make sure it's one of ours. + try { + $decodedCookie = $this->jwtDecode($state[self::FIELD_COOKIE]); + } catch (\Exception $ex) { + throw new InvalidValueException('Could not use supplied jsConnect SSO token: '.$ex->getMessage()); + } + $nonce = self::validateFieldExists(self::FIELD_NONCE, $decodedCookie, 'ssoToken'); + $cookie = $state[self::FIELD_COOKIE]; + unset($state[self::FIELD_COOKIE]); + } else { + $nonce = JWT::urlsafeB64Encode(openssl_random_pseudo_bytes(15)); + $cookie = $this->jwtEncode([self::FIELD_NONCE => $nonce]); + } $requestJWT = $this->jwtEncode([ - 'st' => $state + [ + 'st' => [ self::FIELD_NONCE => $nonce, - ], + ] + $state, 'rurl' => $this->getRedirectUrl(), ]); $requestUrl = $this->getAuthenticateUrlWithSeparator().http_build_query(['jwt' => $requestJWT]); diff --git a/tests/JsConnectServerTest.php b/tests/JsConnectServerTest.php index 3cff702..fb9b353 100644 --- a/tests/JsConnectServerTest.php +++ b/tests/JsConnectServerTest.php @@ -109,4 +109,26 @@ public function testMissingCookie() { '' ); } + + /** + * I should be able to re-use my cookie if I pass it back to another generate request. + */ + public function testCookieReuse(): void { + list($_, $cookie) = $this->jsc->generateRequest(); + sleep(1); + list($_, $cookie2) = $this->jsc->generateRequest([JsConnectServer::FIELD_COOKIE => $cookie]); + $this->assertSame($cookie, $cookie2); + + $decoded2 = $this->jsc->jwtDecode($cookie2); + $this->assertArrayNotHasKey(JsConnectServer::FIELD_COOKIE, $decoded2, "The cookie should be double encoded."); + } + + /** + * I should not be able to re-use any old cookie value for JsConnect. + */ + public function testCookieReuseError(): void { + $this->expectException(InvalidValueException::class); + $this->expectExceptionMessage("Could not use supplied jsConnect SSO token"); + list($_, $cookie) = $this->jsc->generateRequest([JsConnectServer::FIELD_COOKIE => 'cook']); + } }