Skip to content
Open
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 3.2.2 under development

- Enh #101: Refactor `Authentication` middleware authentication failure handling (@olegbaturin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use "Chg" and replace "refactor" to concrete changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- Chg #101: Change PHP constraint in composer.json to 8.1 - 8.4 (@olegbaturin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Chg #101: Change PHP constraint in composer.json to 8.1 - 8.4 (@olegbaturin)

- Chg #104: Bump minimal PHP version to 8.1 (@vjik)
- Enh #104: Explicitly mark readonly properties (@vjik)

Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"roave/infection-static-analysis-plugin": "^1.35",
"spatie/phpunit-watcher": "^1.24.4",
"vimeo/psalm": "^5.26.1 || ^6.10",
"yiisoft/di": "^1.4",
"yiisoft/yii-debug": "dev-master"
},
"autoload": {
Expand All @@ -61,6 +62,7 @@
"source-directory": "config"
},
"config-plugin": {
"di-web": "di-web.php",
"params": "params.php"
}
},
Expand Down
14 changes: 14 additions & 0 deletions config/di-web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

use Yiisoft\Auth\AuthenticationFailureHandler;
use Yiisoft\Auth\AuthenticationFailureHandlerInterface;

/**
* @var array $params
*/

return [
AuthenticationFailureHandlerInterface::class => AuthenticationFailureHandler::class,
];
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@

declare(strict_types=1);

namespace Yiisoft\Auth\Handler;
namespace Yiisoft\Auth;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Http\Status;

/**
* Default authentication failure handler. Responds with "401 Unauthorized" HTTP status code.
*/
final class AuthenticationFailureHandler implements RequestHandlerInterface
final class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit in having a dedicated interface? Ability to configure it with a single line only? @vjik is there a better way?

Copy link
Member

@vjik vjik Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
public function __construct(
private readonly ResponseFactoryInterface $responseFactory,
Expand Down
16 changes: 16 additions & 0 deletions src/AuthenticationFailureHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Auth;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* `AuthenticationFailureHandlerInterface` produces a response when there is a failure authenticating an identity.
*/
interface AuthenticationFailureHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface;
}
21 changes: 7 additions & 14 deletions src/Middleware/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,21 @@

namespace Yiisoft\Auth\Middleware;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Auth\AuthenticationMethodInterface;
use Yiisoft\Auth\Handler\AuthenticationFailureHandler;
use Yiisoft\Auth\AuthenticationFailureHandlerInterface;
use Yiisoft\Strings\WildcardPattern;

/**
* Authentication middleware tries to authenticate and identity using request data.
* `Authentication` middleware tries to authenticate an identity using request data.
* If identity is found, it is set to request attribute allowing further middleware to obtain and use it.
* If identity is not found failure handler is called. By default it is {@see AuthenticationFailureHandler}.
* If identity is not found failure handler is called.
*/
final class Authentication implements MiddlewareInterface
{
/**
* @var RequestHandlerInterface A handler that is called when there is a failure authenticating an identity.
*/
private RequestHandlerInterface $failureHandler;

/**
* @var array Patterns to match to consider the given request URI path optional.
*/
Expand All @@ -34,14 +28,13 @@ final class Authentication implements MiddlewareInterface
*/
private array $wildcards = [];

/**
* @param AuthenticationFailureHandlerInterface $failureHandler A handler that is called when there is a failure authenticating an identity.
*/
public function __construct(
private AuthenticationMethodInterface $authenticationMethod,
ResponseFactoryInterface $responseFactory,
?RequestHandlerInterface $authenticationFailureHandler = null,
private AuthenticationFailureHandlerInterface $failureHandler,
) {
$this->failureHandler = $authenticationFailureHandler ?? new AuthenticationFailureHandler(
$responseFactory
);
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
Expand Down
2 changes: 1 addition & 1 deletion tests/AuthenticationFailureHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Nyholm\Psr7\ServerRequest;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\Auth\Handler\AuthenticationFailureHandler;
use Yiisoft\Auth\AuthenticationFailureHandler;
use Yiisoft\Http\Method;
use Yiisoft\Http\Status;

Expand Down
24 changes: 12 additions & 12 deletions tests/AuthenticationMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Yiisoft\Auth\AuthenticationFailureHandlerInterface;
use Yiisoft\Auth\AuthenticationMethodInterface;
use Yiisoft\Auth\IdentityInterface;
use Yiisoft\Auth\Middleware\Authentication;
Expand Down Expand Up @@ -53,7 +54,7 @@ function (ServerRequestInterface $request) use ($identity) {
}
);

$auth = new Authentication($this->authenticationMethod, $this->responseFactory);
$auth = new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler());
$auth->process($request, $handler);
}

Expand All @@ -80,7 +81,7 @@ public function testShouldSkipCheckForOptionalPath(string $path): void
->expects($this->once())
->method('handle');

$auth = (new Authentication($this->authenticationMethod, $this->responseFactory))
$auth = (new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler()))
->withOptionalPatterns([$path]);
$auth->process($request, $handler);
}
Expand Down Expand Up @@ -108,7 +109,7 @@ public function testShouldNotExecuteHandlerAndReturn401OnAuthenticationFailure()
->expects($this->never())
->method('handle');

$auth = new Authentication($this->authenticationMethod, $this->responseFactory);
$auth = new Authentication($this->authenticationMethod, $this->createAuthenticationFailureHandler());
$response = $auth->process($request, $handler);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals($headerValue, $response->getHeaderLine($header));
Expand Down Expand Up @@ -137,34 +138,33 @@ public function testCustomAuthenticationFailureResponse(): void
->expects($this->never())
->method('handle');

$failureResponse = 'test custom response';
$failureResponseBody = 'test custom response';

$auth = new Authentication(
$this->authenticationMethod,
$this->responseFactory,
$this->createAuthenticationFailureHandler($failureResponse)
$this->createAuthenticationFailureHandler($failureResponseBody)
);
$response = $auth->process($request, $handler);
$this->assertEquals(401, $response->getStatusCode());
$this->assertEquals($headerValue, $response->getHeaderLine($header));
$this->assertEquals($failureResponse, (string)$response->getBody());
$this->assertEquals($failureResponseBody, (string)$response->getBody());
}

public function testImmutability(): void
{
$original = new Authentication(
$this->authenticationMethod,
$this->responseFactory
$this->createAuthenticationFailureHandler()
);

$this->assertNotSame($original, $original->withOptionalPatterns(['test']));
}

private function createAuthenticationFailureHandler(string $failureResponse): RequestHandlerInterface
private function createAuthenticationFailureHandler(string $failureResponseBody = 'Authentication failed'): AuthenticationFailureHandlerInterface
{
return new class ($failureResponse, new Psr17Factory()) implements RequestHandlerInterface {
return new class ($failureResponseBody, new Psr17Factory()) implements AuthenticationFailureHandlerInterface {
public function __construct(
private readonly string $failureResponse,
private readonly string $failureResponseBody,
private readonly ResponseFactoryInterface $responseFactory,
) {
}
Expand All @@ -174,7 +174,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$response = $this->responseFactory->createResponse(Status::UNAUTHORIZED);
$response
->getBody()
->write($this->failureResponse);
->write($this->failureResponseBody);
return $response;
}
};
Expand Down
41 changes: 41 additions & 0 deletions tests/ConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Request\Body\Tests;

use Nyholm\Psr7\Factory\Psr17Factory;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseFactoryInterface;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
use Yiisoft\Auth\AuthenticationFailureHandler;
use Yiisoft\Auth\AuthenticationFailureHandlerInterface;

use function dirname;

final class ConfigTest extends TestCase
{
public function testAuthenticationFailureHandler(): void
{
$container = $this->createContainer();

$failureHandler = $container->get(AuthenticationFailureHandlerInterface::class);
$this->assertInstanceOf(AuthenticationFailureHandler::class, $failureHandler);
}

private function createContainer(): Container
{
return new Container(
ContainerConfig::create()->withDefinitions([
ResponseFactoryInterface::class => Psr17Factory::class,
...$this->getContainerDefinitions(),
])
);
}

private function getContainerDefinitions(): array
{
return require dirname(__DIR__) . '/config/di-web.php';
}
}
Loading