How do I implement CSRF protection with Slim 4 and slim/csrf? - php

Slim 4 is already here and I am trying to move to Slim 4. Everything is great, but CSRF returns an error when i try to implement it. I tried the simplest setup, but I get this error:
Message: Argument 2 passed to Slim\Csrf\Guard::__invoke() must be an instance of Psr\Http\Message\ResponseInterface, instance of Slim\Routing\RouteRunner given, called in /Volumes/Web/slim/vendor/slim/slim/Slim/MiddlewareDispatcher.php on line 180
File: /Volumes/Web/slim/vendor/slim/csrf/src/Guard.php
Here is my code:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Csrf\Guard;
require __DIR__ . '/../vendor/autoload.php';
/**
* Instantiate App
*
* In order for the factory to work you need to ensure you have installed
* a supported PSR-7 implementation of your choice e.g.: Slim PSR-7 and a supported
* ServerRequest creator (included with Slim PSR-7)
*/
$app = AppFactory::create();
$app->add(Guard::class);
// Add Routing Middleware
$app->addRoutingMiddleware();
/*
* Add Error Handling Middleware
*
* #param bool $displayErrorDetails -> Should be set to false in production
* #param bool $logErrors -> Parameter is passed to the default ErrorHandler
* #param bool $logErrorDetails -> Display error details in error log
* which can be replaced by a callable of your choice.
* Note: This middleware should be added last. It will not handle any exceptions/errors
* for middleware added after it.
*/
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
// Define app routes
$app->get('/', function (Request $request, Response $response, $args) {
$response->getBody()->write('Hello');
return $response;
});
// Run app
$app->run();
Any help is greatly appreciated! Thanks!

The package is not compatible with Slim4. I wrote a wrapper so you can use it.
`
<?php
declare(strict_types=1);
namespace App\Application\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Csrf\Guard as Guard;
class CsrfMiddleware extends Guard implements Middleware
{
/**
* Process middleware
*
* #param ServerRequestInterface $request request object
* #param RequestHandlerInterface $handler handler object
*
* #return ResponseInterface response object
*/
public function process(Request $request, RequestHandler $handler): Response
{
$this->validateStorage();
// Validate POST, PUT, DELETE, PATCH requests
if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
$body = $request->getParsedBody();
$body = $body ? (array) $body : [];
$name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false;
$value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false;
if (!$name || !$value || !$this->validateToken($name, $value)) {
// Need to regenerate a new token, as the validateToken removed the current one.
$request = $this->generateNewToken($request);
$failureCallable = $this->getFailureCallable();
return $failureCallable($request, $handler);
}
}
// Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored
if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) {
$request = $this->generateNewToken($request);
} elseif ($this->persistentTokenMode) {
$pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken();
$request = $this->attachRequestAttributes($request, $pair);
}
// Enforce the storage limit
$this->enforceStorageLimit();
return $handler->handle($request);
}
/**
* Getter for failureCallable
*
* #return callable|\Closure
*/
public function getFailureCallable()
{
if (is_null($this->failureCallable)) {
$this->failureCallable = function (Request $request, RequestHandler $handler): Response {
$response = $handler->handle($request);
$stream = $response->getBody();
$stream->write('CSRF fail');
return $response->withStatus(400);
};
}
return $this->failureCallable;
}
}
`

The relevant bit is:
$app->add(Guard::class);
The signature of middleware callbacks has changed. In Slim/3 it used to be like this:
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface
... and then the method had to call $next like $next($request, $response).
In Slim/4 it's like this:
public function __invoke(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
.. and the internal call to $handler is $handler->handle($request).
The library does not seem to have been updated for Slim/4. It declares Slim/3 as dev (?) dependency in composer.json and mentions in README.md. Perhaps it isn't very difficult to either fix the library or write a compatible wrapper on top of it but if you aren't familiar with the overall ecosystem it's probably easier to install a replacement.

Related

Action executing twice when middleware is added Slim framework

I'm building a simple app based on the excellent Slim 4 Tutorial https://odan.github.io/2019/11/05/slim4-tutorial.html
The structure is still fairly similar to the tutorial.
Every time I call any of my endpoints using Postman it executes my Actions twice (ie: \App\Action\UserGetAction) for a Get. I can confirm this is happening in my logs.
if I comment out the AuthMiddleware file in config/middleware.php the duplicate action stops happening.
Any ideas?
I have created my own Auth middleware and added it in the config/middleware.php file:
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
use SlimSession\Helper;
use App\Factory\SessionFactory;
use App\Factory\AuthMiddleware;
return function (App $app) {
// Parse json, form data and xml
$app->addBodyParsingMiddleware();
// Add the Slim built-in routing middleware
$app->addRoutingMiddleware();
$app->add(BasePathMiddleware::class);
// Catch exceptions and errors
$app->add(ErrorMiddleware::class);
$app->add(AuthMiddleware::class); // <--- here
$loggerFactory = $app->getContainer()->get(\App\Factory\LoggerFactory::class);
$logger = $loggerFactory->addFileHandler('error.log')->createInstance('error');
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);
};
for simplicity I have stripped out pretty much everything in the AuthMiddleware:
namespace App\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;
use GuzzleHttp\Client;
use Exception;
use App\Factory\LoggerFactory;
class AuthMiddleware
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerFactory $logger)
{
$this->logger = $logger
->addFileHandler('user_edit.log')
->addConsoleHandler()
->createInstance('user_edit');
}
/**
* Example middleware invokable class
*
* #param ServerRequest $request PSR-7 request
* #param RequestHandler $handler PSR-15 request handler
*
* #return Response
*/
public function __invoke(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
return $handler->handle($request);
}
} //Class
here are my routes in config/routes.php:
$app->post('/users', \App\Action\UserCreateAction::class);
$app->map(['PUT', 'PATCH'],'/users/{id}[/{system}]', \App\Action\UserUpdateAction::class);
$app->delete('/users/{id}[/{system}]', \App\Action\UserDeleteAction::class);
$app->get('/users/{id}[/{system}]', \App\Action\UserGetAction::class);
$app->get('/extid/{id}[/{system}]', \App\Action\ExtidGetAction::class);
I have confirmed that I'm only running $app-run() once --- at the end of my index.php
require __DIR__ . '/../config/bootstrap.php';
$app->run();
There is error in __invoke method of your middleware.
You call handler queue twice:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// here you call handle queue for the second time
// so you get the second duplicated version of response
// this is the reason why you actions called twice
return $handler->handle($request);
}
One of the solutions is below:
public function __invoke(Request $request, RequestHandler $handler): Response
{
// here you call handler queue first time
// so you get response
$response = $handler->handle($request);
$headers = $request->getHeaders();
$eauth = $headers["Authorization"][0];
$this->logger->info($eauth);
// return response you already have
return $response;
}

Slim 3 framework - Passing data from middleware to controller - action args

I'm using a controller/middleware build with slim 3 and i want from the middleware attached to a group, to pass some data to the $args parameter in my controller - action.
Here's some code:
class MyController
{
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function index(Request $request, Response $response, $args) {
return $this->container->get('renderer')->render($response, "index.html.twig", $args);
}
}
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
// do some stuff to inject further down to $args some data
return $next($request, $response);
}
}
$app->group('/group', function () use ($app){
//routes
})->add(new MyMiddleware());
My use case is to send stuff to all the views rendered by the actions of these controllers, so i'm also fine with other ways to do this :)
Thanks.
so you need just pass data from Middleware to Controller
what about
class MyMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
$request = $request->withAttribute('myMagicArgument', 42);
return $next($request, $response);
}
}
and then in controller
class MyController
{
//...
public function index(Request $request, Response $response) {
$yourAttributeFromMiddleware = $request->getAttribute('myMagicArgument');
//...
}
}
For completeness, I'm going to extend the excellent answer given #jDolba
Unfortunately, though it got me going in the right direction, it still took a little experimentation to get everything working.
Basically, as explained in the slim router docs
The route callback signature is determined by a route strategy. By
default, Slim expects route callbacks to accept the request, response,
and an array of route placeholder arguments. This is called the
RequestResponse strategy. However, you can change the expected route
callback signature by simply using a different strategy. As an
example, Slim provides an alternative strategy called
RequestResponseArgs that accepts request and response, plus each route
placeholder as a separate argument. Here is an example of using this
alternative strategy; simply replace the foundHandler dependency
provided by the default \Slim\Container:
$c = new \Slim\Container();
$c['foundHandler'] = function() {
return new \Slim\Handlers\Strategies\RequestResponseArgs();
};
$app = new \Slim\App($c);
$app->get('/hello/{name}', function ($request, $response, $name) {
return $response->write($name);
});
You can provide your own route strategy by implementing the
\Slim\Interfaces\InvocationStrategyInterface.
however, for the task of injecting some standardised data into the $args[] array, the default \Slim\Handlers\Strategies\RequestResponse class does everything it needs to minus injecting the data.
As such, I simply extended that class:
<?php
namespace MyProject\Handlers\Strategies;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Handlers\Strategies\RequestResponse;
class SomeNewInvocationStrategy extends RequestResponse
{
/**
* Invoke a route callable.
*
* #param callable $callable The callable to invoke using the strategy.
* #param ServerRequestInterface $request The request object.
* #param ResponseInterface $response The response object.
* #param array $routeArguments The route's placholder arguments
*
* #return ResponseInterface|string The response from the callable.
*/
public function __invoke( callable $callable, ServerRequestInterface $request, ResponseInterface $response, array $routeArguments)
{
$routeArguments['test'] = 'testing testing 123';
return parent::__invoke( $callable, $request, $response, $routeArguments );
}
}
My container declaration looks like this:
<?php
use Slim\App;
return function (App $app) {
$container = $app->getContainer();
$container['foundHandler'] = function() {
return new MyProject\Handlers\Strategies\SomeNewInvocationStrategy();
};
}
Then in all of my controller actions I have access to $args['test']. Further, this can be passed straight through to any Twig views.
This is useful for tasks like access control where by I always want to load the roles of users before processing the request but I'm sure there'll be many other use-cases for it.
I Hope this helps somebody.

How to 'use' packages in my own Laravel middleware?

I wrote a localization middleware in Laravel using the LaravelGettext package which looks like this:
<?php
namespace App\Http\Middleware;
use Closure;
class Locale {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
if ($request->method() === 'GET') {
$segment = $request->segment(1);
if (!in_array($segment, config('laravel-gettext.supported-locales'))) {
$segments = $request->segments();
$fallback = session('locale') ?: config('laravel-gettext.fallback-locale');
$segments = array_prepend($segments, $fallback);
return redirect()->to(implode('/', $segments));
}
session(['locale' => $segment]);
LaravelGettext::setLocale($segment);
}
return $next($request);
}
}
I am routing into the middleware via:
Route::prefix('{lang?}')->middleware('locale')->group(function () {
...
}
Running through the middleware gives me this error though:
"Class 'App\Http\Middleware\LaravelGettext' not found"
So I figured I might have to import the LaravelGettext package manually by adding:
use Xinax\LaravelGettext\LaravelGettext;
Which now gives me this Exception:
"Non-static method Xinax\LaravelGettext\LaravelGettext::setLocale() should not be called statically"
Which makes me wonder: Is there even a valid option to access the package inside a middleware? Or did I drive into a design flaw here?
Well, it just came to me that I had to import the Facade, not the actual class itself. So adding
use Xinax\LaravelGettext\Facades\LaravelGettext;
made it finally work!

Slim\\Route::__invoke() with Middleware is not accepting the right $request & $response

I am currently writing a REST API using Slim Framework 3 and implementing Middleware for basic authentication.
My routing goes like this:
$app->group('/api', function () use ($app, $pdo) {
$this->group('/v1', function () use ($app, $pdo) {
// Guest Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/authentication.php';
});
// Authenticated Routes
$this->group('', function() use ($app, $pdo) {
require_once '../app/api/v1/test.php';
})->add(new \App\Middleware\AuthMiddleware($pdo));
});
});
In the AuthMiddleware class I am using the __invoke method in the following way:
namespace App\Middleware;
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
class AuthMiddleware extends Middleware {
/**
* #param Request $request
* #param Response $response
* #param $next
* #return Response
*/
public function __invoke(Request $request, Response $response, $next) {
$response = $next($response, $request);
return $response;
}
}
And I'm getting the following error:
Argument 1 passed to Slim\Route::__invoke() must implement interface Psr\Http\Message\ServerRequestInterface, instance of Slim\Http\Response given
on the following line:
$response = $next($response, $request);
What is happening? any ideas? I've been eating myself over this for 2 hours :(
Thanks a bunch!
Stupidly.. I noticed that on
$response = $next($response, $request);
I reversed the parameters.. should be
$response = $next($request, $response);
Blaahh... my head hurts.

Slim 3 how to use logger inside middleware function

I have written this router after creating everything using the official:
$ php composer.phar create-project slim/slim-skeleton [my-app-name]
this is my routes.php file
<?php
require_once(__DIR__."/../bootstrap.php");
// Routes
class OwnsPost
{
/**
* Example middleware invokable class
*
* #param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
* #param \Psr\Http\Message\ResponseInterface $response PSR7 response
* #param callable $next Next middleware
*
* #return \Psr\Http\Message\ResponseInterface
*/
public function __invoke($request, $response, $next)
{
if($request->getQueryParams() && $request->getQueryParams()['pid']){
$pid = intval($request->getQueryParams()['pid']);
if($pid == 0){
$this->logger->info("illegal pid call");
return false;
}
$cpost = get_post($pid);
if($cpost->post_author != get_current_user()){
$this->logger->info("wrong current user, tried accessing postid " . $cpost->ID . " with user ". get_current_user());
return false;
}
}else{
$this->logger->info("illegal pid call");
return false;
}
// $response->getBody()->write('BEFORE');
$response = $next($request, $response);
// $response->getBody()->write('AFTER');
return $response;
}
}
$app->get('/campaignedit/setcharitable/{id}', function ($request, $response, $id) {
// Sample log message
$this->logger->info("setcharitable '/' route " . $id);
// Render index view
return $this->renderer->render($response, 'index2.php', $id);
})->add( new OwnsPost() );
$this->logger works in the routing section, but not in the middleware section.
I get an
Fatal error: Call to a member function info() on a non-object in
/var/www/html/japi/src/routes.php on line 33
There is no member variable $logger in your middleware class. So, first add one.
protected $logger;
Next, add a constructor that accepts $logger as an argument.
public function __construct($logger)
{
$this->logger = $logger;
}
Last, when you initialize your middleware, pass the instance of your Logger.
$ownsPost = new OwnsPost($this->logger);
I need obviously to call to
global $app

Categories