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: 1 addition & 1 deletion .github/workflows/bc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0']
['8.1']
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
os: >-
['ubuntu-latest', 'windows-latest']
php: >-
['8.0', '8.1', '8.2', '8.3', '8.4']
['8.1', '8.2', '8.3', '8.4']
2 changes: 1 addition & 1 deletion .github/workflows/composer-require-checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0', '8.1', '8.2', '8.3', '8.4']
['8.1', '8.2', '8.3', '8.4']
2 changes: 1 addition & 1 deletion .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:
os: >-
['ubuntu-latest']
php: >-
['8.0', '8.1', '8.2', '8.3', '8.4']
['8.1', '8.2', '8.3', '8.4']
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## 3.2.1 under development

- no changes in this release.
- Enh #101: Refactor `Authentication` middleware authentication failure handling (@olegbaturin)
- Chg #101: Change PHP constraint in composer.json to 8.1 - 8.4 (@olegbaturin)

## 3.2.0 September 11, 2025

Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
}
],
"require": {
"php": "8.0 - 8.4",
"php": "8.1 - 8.4",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0 || ^2.0",
"psr/http-server-handler": "^1.0",
Expand All @@ -44,6 +44,7 @@
"roave/infection-static-analysis-plugin": "^1.25",
"spatie/phpunit-watcher": "^1.23.6",
"vimeo/psalm": "^4.30 || ^5.26.1 || ^6.10",
"yiisoft/di": "^1.4",
"yiisoft/yii-debug": "dev-master || dev-php80"
},
"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?

{
public function __construct(private 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 @@ -12,6 +12,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 @@ -52,7 +53,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 Down Expand Up @@ -81,7 +82,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 @@ -109,7 +110,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 @@ -138,33 +139,32 @@ 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 {
public function __construct(private string $failureResponse, private ResponseFactoryInterface $responseFactory)
return new class ($failureResponseBody, new Psr17Factory()) implements AuthenticationFailureHandlerInterface {
public function __construct(private string $failureResponseBody, private ResponseFactoryInterface $responseFactory)
{
}

Expand All @@ -173,7 +173,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