Accessing slim request/app from inside custom authenticator
sudofox opened this issue · 2 comments
I'm very new to Slim in general, and have been searching for a simple way to do take an Authorization: Basic ...
header and use it to make decisions. I found your library and thankfully it seems to work. However, I'm struggling a bit. I need to do three things:
- Get basic auth and then check if the creds are correct. This is working fine, and results in a
$user
User object (from Propel ORM). - Get the current route and then check if the user has permissions to access it.
- Somehow make
$user
available to every route handler as I will need to be able to know who authenticated
Right now I have no clue how to do 2 and 3.
class InternalApiAuthenticator implements AuthenticatorInterface {
public function __invoke(array $arguments): bool {
if (!isset($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"])) {
return false;
}
$username = $_SERVER["PHP_AUTH_USER"];
$password = $_SERVER["PHP_AUTH_PW"];
$user = new App\RBAC\UserQuery;
$user = $user->findOneByUsername($username);
if (!$user) {
return false;
}
// check password
if (!$user->check_password($password)) {
return false;
}
$the_route = getRouteSomehow(); // /foo/bar/baz
$PathUtil = new App\RBAC\Action\PathUtil;
$actionPath = $PathUtil->apiToActionPath($the_route); // Foo.Bar.Baz
if (!$user->check_permissions($actionPath)) {
return false;
}
return true;
}
}
and this is where I'm adding it in:
$app = AppFactory::create();
$app->add(new HttpBasicAuthentication([
"path" => "/",
"realm" => "Protected",
"relaxed" => ["foo.bar.redacted.com"],
"authenticator" => new InternalApiAuthenticator
]));
$app->get('/foo/bar/baz', function (Request $request, Response $response, $args) {
$response->getBody()->write(json_encode($_SERVER, JSON_PRETTY_PRINT));
return $response;
});
$app->run();
Is there any way to do this? I've been spinning my wheels for a bit.
I've gotten back to digging into this and I think it might be possible to use a rule or something, since the __invoke method required by the RuleInterface actually takes ServerRequestInterface ...? It seems like undocumented functionality though
This all seems rather flimsy to me though -- still unclear how I would first authenticate the user and then get the API method to check against their permissions. Maybe I'll have to drop this library or something but I feel like there's an obvious answer I'm missing here since being able to check if a user has permissions to access a particular route beyond a hardcoded set of credentials/paths should be elementary.
I figured out an easier way to do it without using this library (sorry!)
I'm putting it here to save anyone else the time.
Here's a stripped down version that checks for basic auth and such, Slim 4 of course.
<?php
use YourApp\AuthMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Routing\RouteContext;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
class AuthMiddleware implements MiddlewareInterface {
/**
* @var ResponseFactoryInterface
*/
private $responseFactory;
/**
* @param ResponseFactoryInterface $responseFactory
*/
public function __construct(ResponseFactoryInterface $responseFactory) {
$this->responseFactory = $responseFactory;
}
/**
* @inheritDoc
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
$failure = $this->responseFactory->createResponse();
$failure->getBody()->write('Authorization denied');
$failure = $failure->withStatus(401);
if (!isset($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"])) {
// auth not provided
return $failure->withAddedHeader("X-Debug", "auth not provided");
}
$username = $_SERVER["PHP_AUTH_USER"];
$password = $_SERVER["PHP_AUTH_PW"];
// do your auth stuff here
$auth_condition_that_should_be_true = true;
$user = [ "id" => 1 ]; // whatever data or object you need
if (!$auth_condition_that_should_be_true) {
// user doesn't exist
return $failure->withAddedHeader("X-Debug", "user doesn't exist");
}
// now we can check that the user has permission
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$the_route = $route->getPattern(); // /foo/bar/baz
$the_user_has_permission_for_route = true;
if (!$the_user_has_permission_for_route) {
// user doesn't have permission
return $failure->withAddedHeader("X-Debug", "no permission for action");
}
// load the user so it's accessible in the request handler
$newRequest = $request->withAttribute("user", $user);
// Keep processing middleware queue as normal
return $handler->handle($newRequest);
}
}
And you can then do this:
<?php
// ... composer and whatever else ...
use YourApp\AuthMiddleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
$app = AppFactory::create();
$app->addMiddleware(new AuthMiddleware($app->getResponseFactory()));
$app->addRoutingMiddleware();
$app->get('/your/api/path', function (Request $request, Response $response, $args) {
// you can get your user stuff here
$user = $request->getAttribute("user");
$result["user"] = [
"id" => $user->getId(),
"username" => $user->getUsername(),
];
$response->getBody()->write(json_encode($result, JSON_PRETTY_PRINT));
return $response;
});
$app->run();