From d45d5d38ca99781e869028f44cf72349639701bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronald=20M=C3=A1rf=C3=B6ldi?= Date: Sun, 23 Apr 2023 20:38:49 +0200 Subject: [PATCH 1/3] fixes AB#72532 Not send bearer token ends up with 500 error (#9) --- src/Security/Authentication/ApiTokenAuthenticator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Security/Authentication/ApiTokenAuthenticator.php b/src/Security/Authentication/ApiTokenAuthenticator.php index 087b23d..19d5212 100644 --- a/src/Security/Authentication/ApiTokenAuthenticator.php +++ b/src/Security/Authentication/ApiTokenAuthenticator.php @@ -30,6 +30,9 @@ public function supports(Request $request): bool public function authenticate(Request $request): Passport { [$userId, $token] = $this->getCredentials($request); + if (empty($userId) || empty($token)) { + throw new AuthenticationException('Unauthorized request!'); + } $credentialsChecker = function (string $token, ApiTokenUserInterface $user): bool { return $this->checkCredentials($token, $user); }; From 27624164e732c984f5cf52155edf8cc76fc77cef Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Mon, 24 Jul 2023 14:09:41 +0200 Subject: [PATCH 2/3] HttpUtil: Move creating RefreshTokenExistenceCookie to `storeRefreshTokenOnResponse` (#10) * Move creating `RefreshTokenExistenceCookie` from method `storeJwtOnResponse` to `storeRefreshTokenOnResponse` in HttpUtil * Fix CS * Fix CS --- src/HttpClient/OAuth2HttpClient.php | 1 - src/Util/HttpUtil.php | 15 ++--- tests/Util/HttpUtilTest.php | 85 +++++++++++++++++++++++++++++ tests/Util/JwtUtilTest.php | 4 +- tests/bootstrap.php | 3 +- 5 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 tests/Util/HttpUtilTest.php diff --git a/src/HttpClient/OAuth2HttpClient.php b/src/HttpClient/OAuth2HttpClient.php index 4ef8e3d..95c4cc5 100644 --- a/src/HttpClient/OAuth2HttpClient.php +++ b/src/HttpClient/OAuth2HttpClient.php @@ -25,7 +25,6 @@ public function __construct( private readonly HttpClientInterface $client, private readonly OAuth2Configuration $configuration, private readonly Serializer $serializer, - ) { } diff --git a/src/Util/HttpUtil.php b/src/Util/HttpUtil.php index cc597ad..8d32c55 100644 --- a/src/Util/HttpUtil.php +++ b/src/Util/HttpUtil.php @@ -110,16 +110,9 @@ public function storeJwtOnResponse(Response $response, Token $token, DateTimeImm $signature, $lifetime ); - $refreshTokenExistenceCookie = $this->createCookie( - $this->cookieConfiguration->getRefreshTokenExistenceCookieName(), - '1', - $this->cookieConfiguration->getRefreshTokenLifetime(), - false - ); $response->headers->setCookie($payloadCookie); $response->headers->setCookie($signatureCookie); - $response->headers->setCookie($refreshTokenExistenceCookie); } public function storeRefreshTokenOnResponse(Response $response, RefreshTokenDto $refreshTokenDto): void @@ -130,7 +123,15 @@ public function storeRefreshTokenOnResponse(Response $response, RefreshTokenDto $this->cookieConfiguration->getRefreshTokenLifetime(), ); + $refreshTokenExistenceCookie = $this->createCookie( + $this->cookieConfiguration->getRefreshTokenExistenceCookieName(), + '1', + $this->cookieConfiguration->getRefreshTokenLifetime(), + false + ); + $response->headers->setCookie($refreshTokenCookie); + $response->headers->setCookie($refreshTokenExistenceCookie); } public function storeDeviceIdOnResponse(Response $response, string $deviceId): void diff --git a/tests/Util/HttpUtilTest.php b/tests/Util/HttpUtilTest.php new file mode 100644 index 0000000..d4d026c --- /dev/null +++ b/tests/Util/HttpUtilTest.php @@ -0,0 +1,85 @@ +jwtConfiguration = new JwtConfiguration( + audience: 'anz', + algorithm: 'ES256', + publicCert: base64_decode('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTQp5eFNiQm1DK0hSYWF2Z1dLM25aNG1HNFlmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', true), + privateCert: base64_decode('LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU9OdDBIdEdzUGdRRytKY2VGUk5GdlRZMVVVeDVITTdqQzNVS1ZHRHBlS0tvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTXl4U2JCbUMrSFJhYXZnV0szblo0bUc0WQpmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=', true), + lifetime: 3_600 + ); + $this->jwtUtil = new JwtUtil($this->jwtConfiguration); + + $cookieConfiguration = new CookieConfiguration( + domain: '.example.com', + secure: true, + jwtPayloadCookieName: 'qux', + jwtSignatureCookieName: 'quux', + deviceIdCookieName: 'corge', + refreshTokenCookieName: 'garply', + refreshTokenExistenceCookieName: 'grault', + refreshTokenLifetime: 3600, + ); + + $this->httpUtil = new HttpUtil( + cookieConfiguration: $cookieConfiguration, + jwtConfiguration: $this->jwtConfiguration, + authRedirectDefaultUrl: 'https://example.com', + authRedirectQueryUrlAllowedPattern: '^https?://(.*)\.example\.com$' + ); + } + + public function testStoreJwtOnResponse(): void + { + $response = new Response(); + $token = $this->jwtUtil->create('123'); + $expireAt = new DateTimeImmutable(sprintf('+%d seconds', $this->jwtConfiguration->getLifetime())); + $this->httpUtil->storeJwtOnResponse($response, $token, $expireAt); + + $cookies = $response->headers->getCookies(); + $this->assertCount(2, $cookies); + + /** @var Cookie $payloadCookie */ + $payloadCookie = current( + array_filter($cookies, static function (Cookie $cookie) { + return $cookie->getName() === 'qux'; + }) + ); + + /** @var Cookie $signCookie */ + $signCookie = current( + array_filter($cookies, static function (Cookie $cookie) { + return $cookie->getName() === 'quux'; + }) + ); + + $this->assertSame($token->toString(), $payloadCookie->getValue() . '.' . $signCookie->getValue()); + $this->assertTrue($payloadCookie->isSecure()); + $this->assertFalse($payloadCookie->isHttpOnly()); + $this->assertSame('.example.com', $payloadCookie->getDomain()); + + $this->assertTrue($signCookie->isSecure()); + $this->assertTrue($signCookie->isHttpOnly()); + $this->assertSame('.example.com', $signCookie->getDomain()); + } +} diff --git a/tests/Util/JwtUtilTest.php b/tests/Util/JwtUtilTest.php index cd56f3f..cad0bd5 100644 --- a/tests/Util/JwtUtilTest.php +++ b/tests/Util/JwtUtilTest.php @@ -22,8 +22,8 @@ protected function setUp(): void $this->jwtConfiguration = new JwtConfiguration( audience: 'anz', algorithm: 'ES256', - publicCert: base64_decode('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTQp5eFNiQm1DK0hSYWF2Z1dLM25aNG1HNFlmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='), - privateCert: base64_decode('LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU9OdDBIdEdzUGdRRytKY2VGUk5GdlRZMVVVeDVITTdqQzNVS1ZHRHBlS0tvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTXl4U2JCbUMrSFJhYXZnV0szblo0bUc0WQpmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo='), + publicCert: base64_decode('LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTQp5eFNiQm1DK0hSYWF2Z1dLM25aNG1HNFlmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==', true), + privateCert: base64_decode('LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU9OdDBIdEdzUGdRRytKY2VGUk5GdlRZMVVVeDVITTdqQzNVS1ZHRHBlS0tvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFT0hIQzMvVDZ3cnVNTk40OTBqVE1maXNFa1BoTXl4U2JCbUMrSFJhYXZnV0szblo0bUc0WQpmVDRxMmF1L3V4TktBTjJvODJOdW84VTQ0ZXZkcExkQUhBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=', true), lifetime: 3_600 ); $this->jwtUtil = new JwtUtil($this->jwtConfiguration); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9b0a80c..53ab368 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,5 +2,4 @@ declare(strict_types=1); -require dirname(__DIR__).'/vendor/autoload.php'; - +require dirname(__DIR__) . '/vendor/autoload.php'; From 280bb99833eb7a17b7907a1b6f9ca230943da3b3 Mon Sep 17 00:00:00 2001 From: Andrej Hudec Date: Tue, 25 Jul 2023 15:06:35 +0200 Subject: [PATCH 3/3] Create jwt with custom claims (#11) * Create jwt with custom claims * Fix typo --- src/Util/JwtUtil.php | 21 ++++++++++++++------- tests/Util/JwtUtilTest.php | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Util/JwtUtil.php b/src/Util/JwtUtil.php index 91f8467..efc5cff 100644 --- a/src/Util/JwtUtil.php +++ b/src/Util/JwtUtil.php @@ -31,9 +31,11 @@ public function __construct( /** * Can be used for creating a valid JWT. Useful especially for test environment. * + * @param array $claims + * * @throws MissingConfigurationException */ - public function create(string $authId, DateTimeImmutable $expiresAt = null): Plain + public function create(string $authId, DateTimeImmutable $expiresAt = null, array $claims = []): Plain { $privateCert = $this->jwtConfiguration->getPrivateCert(); @@ -41,16 +43,21 @@ public function create(string $authId, DateTimeImmutable $expiresAt = null): Pla throw MissingConfigurationException::createForPrivateCertPath(); } - return (new Builder(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates())) + $builder = (new Builder(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates())) ->permittedFor($this->jwtConfiguration->getAudience()) ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($expiresAt ?: new DateTimeImmutable(sprintf('+%d seconds', $this->jwtConfiguration->getLifetime()))) - ->relatedTo($authId) - ->getToken( - $this->jwtConfiguration->getAlgorithm()->signer(), - InMemory::plainText($privateCert) - ); + ->relatedTo($authId); + + foreach ($claims as $key => $value) { + $builder->withClaim($key, $value); + } + + return $builder->getToken( + $this->jwtConfiguration->getAlgorithm()->signer(), + InMemory::plainText($privateCert) + ); } public function validate(Token\Plain $token): bool diff --git a/tests/Util/JwtUtilTest.php b/tests/Util/JwtUtilTest.php index cad0bd5..d75485f 100644 --- a/tests/Util/JwtUtilTest.php +++ b/tests/Util/JwtUtilTest.php @@ -44,4 +44,22 @@ public function testCreate(): void ); $this->assertSame(['anz'], $jwt->claims()->get(RegisteredClaims::AUDIENCE)); } + + /** + * @throws MissingConfigurationException + */ + public function testCreateWithClaims(): void + { + $expireAt = new DateTimeImmutable(sprintf('+%d seconds', $this->jwtConfiguration->getLifetime())); + $jwt = $this->jwtUtil->create('123', $expireAt, ['foo' => 'bar', 'qux' => 'quux']); + $this->assertInstanceOf(Plain::class, $jwt); + $this->assertSame('123', $jwt->claims()->get(RegisteredClaims::SUBJECT)); + $this->assertSame('bar', $jwt->claims()->get('foo')); + $this->assertSame('quux', $jwt->claims()->get('qux')); + $this->assertSame( + $expireAt->getTimestamp(), + $jwt->claims()->get(RegisteredClaims::EXPIRATION_TIME)->getTimestamp() + ); + $this->assertSame(['anz'], $jwt->claims()->get(RegisteredClaims::AUDIENCE)); + } }