Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable response factory #176

Open
wants to merge 1 commit into
base: 3.x
Choose a base branch
from
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions src/JwtAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@
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;
use Psr\Http\Server\RequestHandlerInterface;
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;
Expand Down Expand Up @@ -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 = [])
Expand Down Expand Up @@ -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()
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
49 changes: 48 additions & 1 deletion tests/JwtAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@
namespace Tuupola\Middleware;

use Equip\Dispatch\MiddlewareCollection;
use Exception;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
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\Response;
use Zend\Diactoros\ResponseFactory as ZendResponseFactory;

class JwtAuthenticationTest extends TestCase
{
Expand Down Expand Up @@ -863,6 +867,49 @@ public function testShouldCallErrorAndModifyBody()
$this->assertTrue($dummy);
}

public function testShouldUseCustomResponseFactory()
{
// custom response factory will accumulate status codes used in the createResponse call
if (class_exists(ZendResponseFactory::class)) {
$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);
}
};
} else {
$factory = new class implements ResponseFactoryInterface {
public $used = [];
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
{
$this->used[] = $code; // store the codes used
return (new Response)->withStatus($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()
{

Expand Down