From de66bb36b90aa7f9c33a62e496ca1f693dbede14 Mon Sep 17 00:00:00 2001 From: Andrej Rypo Date: Sun, 13 Oct 2019 11:06:19 +0200 Subject: [PATCH] Configurable response factory PSR-17 response factory can be provided in configuration --- README.md | 12 +++++++++++ src/JwtAuthentication.php | 17 ++++++++++++---- tests/JwtAuthenticationTest.php | 36 ++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9ffd148..d80bbce 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,18 @@ $app->add(new Tuupola\Middleware\JwtAuthentication([ ])); ``` +### Response Factory + +A custom PSR-17 compatible response factory can be provided. If none is provided, PSR-17 implementation auto-discovery is used. Response factory is used to create a new 401 response. + +```php +$app = new Slim\App; + +$app->add(new Tuupola\Middleware\JwtAuthentication([ + "responseFactory" => new MyResponseFactory, +])); +``` + ### Rules The optional `rules` parameter allows you to pass in rules which define whether the request should be authenticated or not. A rule is a callable which receives the request as parameter. If any of the rules returns boolean `false` the request will not be authenticated. diff --git a/src/JwtAuthentication.php b/src/JwtAuthentication.php index fcdf31a..3780c46 100644 --- a/src/JwtAuthentication.php +++ b/src/JwtAuthentication.php @@ -39,6 +39,7 @@ use InvalidArgumentException; use Exception; use Firebase\JWT\JWT; +use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\MiddlewareInterface; @@ -46,7 +47,6 @@ use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use RuntimeException; -use Tuupola\Middleware\DoublePassTrait; use Tuupola\Http\Factory\ResponseFactory; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; @@ -85,7 +85,8 @@ final class JwtAuthentication implements MiddlewareInterface "ignore" => null, "before" => null, "after" => null, - "error" => null + "error" => null, + "responseFactory" => null, ]; public function __construct(array $options = []) @@ -139,7 +140,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $token = $this->fetchToken($request); $decoded = $this->decodeToken($token); } catch (RuntimeException | DomainException $exception) { - $response = (new ResponseFactory)->createResponse(401); + $factory = $this->options['responseFactory'] ?? new ResponseFactory; + $response = $factory->createResponse(401); return $this->processError($response, [ "message" => $exception->getMessage(), "uri" => (string)$request->getUri() @@ -158,7 +160,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface /* Modify $request before calling next middleware. */ if (is_callable($this->options["before"])) { - $response = (new ResponseFactory)->createResponse(200); $beforeRequest = $this->options["before"]($request, $params); if ($beforeRequest instanceof ServerRequestInterface) { $request = $beforeRequest; @@ -452,4 +453,12 @@ private function rules(array $rules): void $this->rules->push($callable); } } + + /** + * Set the response factory. + */ + private function responseFactory(ResponseFactoryInterface $factory = null): void + { + $this->options["responseFactory"] = $factory; + } } diff --git a/tests/JwtAuthenticationTest.php b/tests/JwtAuthenticationTest.php index 6830c06..270ccbc 100644 --- a/tests/JwtAuthenticationTest.php +++ b/tests/JwtAuthenticationTest.php @@ -33,14 +33,16 @@ namespace Tuupola\Middleware; use Equip\Dispatch\MiddlewareCollection; +use Exception; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Tuupola\Http\Factory\ResponseFactory; use Tuupola\Http\Factory\ServerRequestFactory; use Tuupola\Http\Factory\StreamFactory; use Tuupola\Middleware\JwtAuthentication\RequestMethodRule; use Tuupola\Middleware\JwtAuthentication\RequestPathRule; +use Zend\Diactoros\ResponseFactory as ZendResponseFactory; class JwtAuthenticationTest extends TestCase { @@ -863,6 +865,38 @@ public function testShouldCallErrorAndModifyBody() $this->assertTrue($dummy); } + public function testShouldUseCustomResponseFactory() + { + // custom response factory will accumulate status codes used in the createResponse call + $factory = new class extends ZendResponseFactory { + public $used = []; + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + $this->used[] = $code; // store the codes used + return parent::createResponse($code, $reasonPhrase); + } + }; + + $collection = new MiddlewareCollection([ + new JwtAuthentication([ + "secret" => "supersecretkeyyoushouldnotcommittogithub", + "responseFactory" => $factory, + ]) + ]); + + $request = (new ServerRequestFactory)->createServerRequest("GET", "https://example.com/api"); + $response = $collection->dispatch($request, function () { + // this callable is not used anyway due to auth error + throw new Exception('not used'); + }); + + // assert correct response status code + $this->assertEquals(401, $response->getStatusCode()); + + // assert that the custom response factory was used + $this->assertSame([401], $factory->used); + } + public function testShouldAllowUnauthenticatedHttp() {