diff --git a/CHANGELOG.md b/CHANGELOG.md index 43499db..7a856d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 3.3.1 under development -- no changes in this release. +- Bug #124: Fix `HttpBasic` and `HttpBearer` to add `WWW-Authenticate` header instead of overwriting it (@vjik) ## 3.3.0 August 06, 2026 diff --git a/src/Method/HttpBasic.php b/src/Method/HttpBasic.php index ed8c310..0c3f875 100644 --- a/src/Method/HttpBasic.php +++ b/src/Method/HttpBasic.php @@ -63,7 +63,7 @@ public function authenticate(ServerRequestInterface $request): ?IdentityInterfac public function challenge(ResponseInterface $response): ResponseInterface { - return $response->withHeader(Header::WWW_AUTHENTICATE, "Basic realm=\"{$this->realm}\""); + return $response->withAddedHeader(Header::WWW_AUTHENTICATE, "Basic realm=\"{$this->realm}\""); } /** diff --git a/src/Method/HttpBearer.php b/src/Method/HttpBearer.php index 94c6d7e..68cd962 100644 --- a/src/Method/HttpBearer.php +++ b/src/Method/HttpBearer.php @@ -28,7 +28,7 @@ final class HttpBearer extends HttpHeader implements AuthenticatorWithChallengeI public function challenge(ResponseInterface $response): ResponseInterface { - return $response->withHeader(Header::WWW_AUTHENTICATE, "Bearer realm=\"{$this->realm}\""); + return $response->withAddedHeader(Header::WWW_AUTHENTICATE, "Bearer realm=\"{$this->realm}\""); } /** diff --git a/tests/Method/CompositeTest.php b/tests/Method/CompositeTest.php index 4234262..ca38b7b 100644 --- a/tests/Method/CompositeTest.php +++ b/tests/Method/CompositeTest.php @@ -10,6 +10,7 @@ use Psr\Http\Message\ServerRequestInterface; use Yiisoft\Auth\IdentityInterface; use Yiisoft\Auth\Method\Composite; +use Yiisoft\Auth\Method\HttpBasic; use Yiisoft\Auth\Method\HttpBearer; use Yiisoft\Auth\Method\QueryParameter; use Yiisoft\Auth\Tests\Stub\FakeIdentity; @@ -110,6 +111,25 @@ public function testChallengeIsCorrect(): void ); } + public function testChallengeAccumulatesHeadersFromMultipleMethods(): void + { + $response = new Response(400); + $identityRepository = new FakeIdentityRepository($this->createIdentity()); + + $authenticationMethod = new Composite([ + new HttpBearer($identityRepository), + new HttpBasic($identityRepository), + ]); + + $this->assertEquals( + [ + 'Bearer realm="api"', + 'Basic realm="api"', + ], + $authenticationMethod->challenge($response)->getHeader(Header::WWW_AUTHENTICATE), + ); + } + private function createIdentity(): IdentityInterface { return new FakeIdentity('test-id'); diff --git a/tests/Method/HttpBasicTest.php b/tests/Method/HttpBasicTest.php index 3b3350b..c4a808f 100644 --- a/tests/Method/HttpBasicTest.php +++ b/tests/Method/HttpBasicTest.php @@ -136,6 +136,21 @@ public function testCustomRealm(): void ); } + public function testChallengeAddsHeaderInsteadOfOverwritingExistingOne(): void + { + $response = (new Response())->withHeader(Header::WWW_AUTHENTICATE, 'Bearer realm="api"'); + $identityRepository = new FakeIdentityRepository($this->createIdentity()); + $authenticationMethod = new HttpBasic($identityRepository); + + $this->assertEquals( + [ + 'Bearer realm="api"', + 'Basic realm="api"', + ], + $authenticationMethod->challenge($response)->getHeader(Header::WWW_AUTHENTICATE), + ); + } + public function testInvalidHeaderName(): void { $encodeFields = base64_encode('admin:pass'); diff --git a/tests/Method/HttpBearerTest.php b/tests/Method/HttpBearerTest.php index 9d7c202..82b8ee4 100644 --- a/tests/Method/HttpBearerTest.php +++ b/tests/Method/HttpBearerTest.php @@ -78,6 +78,21 @@ public function testCustomRealm(): void ); } + public function testChallengeAddsHeaderInsteadOfOverwritingExistingOne(): void + { + $response = (new Response())->withHeader(Header::WWW_AUTHENTICATE, 'Basic realm="api"'); + $identityRepository = new FakeIdentityRepository($this->createIdentity()); + $authenticationMethod = new HttpBearer($identityRepository); + + $this->assertEquals( + [ + 'Basic realm="api"', + 'Bearer realm="api"', + ], + $authenticationMethod->challenge($response)->getHeader(Header::WWW_AUTHENTICATE), + ); + } + public function testImmutability(): void { $identityRepository = new FakeIdentityRepository($this->createIdentity());