Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/Method/HttpBasic.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

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}\"");
}

/**
Expand Down Expand Up @@ -184,7 +184,7 @@
{
return array_map(
static fn($value) => $value === '' ? null : $value,
explode(':', base64_decode(substr($authToken, 6)), 2),

Check warning on line 187 in src/Method/HttpBasic.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.3-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": @@ @@ { return array_map( static fn($value) => $value === '' ? null : $value, - explode(':', base64_decode(substr($authToken, 6)), 2), + explode(':', base64_decode(substr($authToken, 5)), 2), ); }
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Method/HttpBearer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"");
}

/**
Expand Down
20 changes: 20 additions & 0 deletions tests/Method/CompositeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down
15 changes: 15 additions & 0 deletions tests/Method/HttpBasicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
15 changes: 15 additions & 0 deletions tests/Method/HttpBearerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading