Exclude route from Slim CSRF middleware - php

I'm working on a Slim 3 based application with a Twig frontend and I'm also making a REST API.
I've implemented slimphp\Slim-Csrf for the entire app but I now want to exclude this CSRF check from every "API" routes.
I'm trying to implement the "Option 2" of this post :
Slim3 exclude route from CSRF Middleware
Here is the code :
File App\Middleware\CsrfMiddleware.php :
namespace App\Middleware;
class CsrfMiddleware extends \Slim\Csrf\Guard {
public function processRequest($request, $response, $next) {
// Check if this route is in the "Whitelist"
$route = $request->getAttribute('route');
if ($route->getName() == 'token') {
var_dump('! problem HERE, this middleware is executed after the CsrfMiddleware !');
// supposed to SKIP \Slim\Csrf\Guard
return $next($request, $response);
} else {
// supposed to execute \Slim\Csrf\Guard
return $this($request, $response, $next);
}
}
}
File app\app.php :
$app = new \Slim\App([
'settings' => [
'determineRouteBeforeAppMiddleware' => true
]
]);
require('container.php');
require('routes.php');
$app->add($container->csrf);
$app->add('csrf:processRequest');
File app\container.php :
$container['csrf'] = function ($container) {
return new App\Middleware\CsrfMiddleware;
};
File app\routes.php :
<?php
$app->get('/', \App\PagesControllers\LieuController::class.':home')->setName('home');
$app->post('/api/token', \App\ApiControllers\AuthController::class.'postToken')->setName('token');
When I do a POST request on http://localhost/slim3/public/api/token I've got :
Failed CSRF check!string(70) "! problem HERE, this middleware is executed after the CsrfMiddleware !"
Like if my CsrfMiddleware was executed after \Slim\Csrf\Guard...
Anyone has an idea ?
Thank you.

In Slim 3 the middleware is LIFO (last in first out).
Add the middleware in the opposite direction:
Before
$app->add($container->csrf);
$app->add('csrf:processRequest');
After
$app->add('csrf:processRequest');
$app->add($container->csrf);
Notice: The public directory should not be part of the url
Not correct: http://localhost/slim3/public/api/token
Correct: http://localhost/slim3/api/token
To skip the processing within the middleware, just return the $response object.
// supposed to SKIP \Slim\Csrf\Guard
return $response;

Here is how I achieved this with Slim 3.
1) Create a class that extends \Slim\Csrf\Guard as follows.
The CsrfGuardOverride class is key to enabling or disabling CSRF checking for a path. If the current path is whitelist'ed, then the __invoke() method just skips the core CSRF checking, and proceeds by executing the next middleware layer.
If the current path is not in the whitelist (i.e., CSRF should be checked), then the __invoke method defers to its parent \Slim\Csrf\Guard::__invoke() to handle CSRF in the normal manner.
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use \Slim\Csrf\Guard;
class CsrfGuardOverride extends Guard {
/**
* Invoke middleware
*
* #param ServerRequestInterface $request PSR7 request object
* #param ResponseInterface $response PSR7 response object
* #param callable $next Next middleware callable
*
* #return ResponseInterface PSR7 response object
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
// Set the name of the route we want whitelisted with a name
// prefix of 'whitelist'. check for that here, and add
// any path to the white list
$route = $request->getAttribute('route');
$routeName = $route->getName();
$whitelisted = strpos($routeName, 'whitelist');
// if url is whitelisted from being CSRF checked, then bypass checking by skipping directly to next middleware
if ($whitelisted !== FALSE) {
return $next($request, $response);
}
return parent::__invoke($request, $response, $next);
}
}
2) Register the CsrfGuardOverride class. Be sure to set settings.determineRouteBeforeAppMiddleware => true as this forces Slim to evaluate routes prior to executing any middleware.
// Method on App Class
protected function configureContainer(ContainerBuilder $builder)
{
parent::configureContainer($builder);
$definitions = [
'settings.displayErrorDetails' => true,
'settings.determineRouteBeforeAppMiddleware' => true,
// Cross-Site Request Forgery protection
\App\Middleware\CsrfGuardOverride::class => function (ContainerInterface $container) {
$guard = new \App\Middleware\CsrfGuardOverride;
$guard->setPersistentTokenMode(true); // allow same CSRF token for multiple ajax calls per session
return $guard;
},
'csrf' => DI\get(\App\Middleware\CsrfGuardOverride::class),
// add others here...
];
$builder->addDefinitions($definitions);
}
3) Add the path that you want to by bypass CSRF checking and give it a name with the prefix 'whitelist':
$app->post('/events/purchase', ['\App\Controllers\PurchaseController', 'purchaseCallback'])->setName('whitelist.events.purchase');

Related

Laravel Passport Route [login] not defined

i use Laravel passport for auth
in route api.php
Route::get('/todos', function(){
return 'hello';
})->middleware('auth:api');
but when open localhost:8000/api/todos I see the following error
InvalidArgumentException
Route [login] not defined.
* #return string
*
* #throws \InvalidArgumentException
*/
public function route($name, $parameters = [], $absolute = true)
{
if (! is_null($route = $this->routes->getByName($name))) {
return $this->toRoute($route, $parameters, $absolute);
}
throw new InvalidArgumentException("Route [{$name}] not defined.");
}
/**
* Get the URL for a given route instance.
*
* #param \Illuminate\Routing\Route $route
* #param mixed $parameters
* #param bool $absolute
* #return string
*
* #throws \Illuminate\Routing\Exceptions\UrlGenerationException
*/
protected function toRoute($route, $parameters, $absolute)
{
return $this->routeUrl()->to(
$route, $this->formatParameters($p
I want if the user was not authenticated
Do not redirect to any page and only see the page
Use Postman and set the Header Accept: application/json otherwise Laravel Passport would never know it's an API client and thus redirect to a /login page for the web.
see below image to see where to set the accept parameter:
Did you enter above-mentioned URL directly in browser search bar?
If you did its wrong way because you also need to enter API token with your request__!!
To check either request includes token or not make your own middleware.
Command to create Middleware
php artisan make:middleware CheckApiToken
https://laravel.com/docs/5.6/middleware
change middleware handle method to
public function handle($request, Closure $next)
{
if(!empty(trim($request->input('api_token')))){
$is_exists = User::where('id' , Auth::guard('api')->id())->exists();
if($is_exists){
return $next($request);
}
}
return response()->json('Invalid Token', 401);
}
Like This
Your Url should be like this
http://localhost:8000/api/todos?api_token=API_TOKEN_HERE
You also have to add another header Key: Accept and value: application/json
Check Your Header Request to put
Authorization = Bearer {your token}
In the following of #Eki answer,
This error is because you didn't set "Accept" field in your headers.
To avoid this error, add a middleware with priority to Authenticate to check that:
add an extra middleware with below handler
public function handle($request, Closure $next)
{
if(!in_array($request->headers->get('accept'), ['application/json', 'Application/Json']))
return response()->json(['message' => 'Unauthenticated.'], 401);
return $next($request);
}
set priority in app/Http/Kernel.php
protected $middlewarePriority = [
...
\App\Http\Middleware\MyMiddleware::class, // new middleware
\App\Http\Middleware\Authenticate::class,
...
];
add new middleware to your route
Route::get('/todos', function(){
return 'hello';
})->middleware('MyMiddleware', 'auth:api');
You also have to add another header Key: X-Requested-With and value: XMLHttpRequest

Laravel 5 null cookie crash

Scenario:
Same browser.
Tab 1: logging into my laravel application.
Tab 2: logging into my laravel application.
Tab 2: log off
Tab 1: Click a button that causes a redirect to a route that's protected by: Route::group(['middleware' => 'auth'], function () {
...
Result: Laravel 5 crashes before it gets to my code on:
Stack:
Symfony\Component\Debug\Exception\FatalErrorException Call to a member function setCookie() on null
vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php:184 __construct
vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php:131 fatalExceptionFromError
vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php:116 handleShutdown
vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php:0 addCookieToResponse
vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php:72 handle
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:125 Illuminate\Pipeline\{closure}
vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php:36 handle
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:125 Illuminate\Pipeline\{closure}
vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php:40 handle
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:125 Illuminate\Pipeline\{closure}
vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php:42 handle
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:125 Illuminate\Pipeline\{closure}
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:101 call_user_func:{/home/vagrant/dev/opus-web-app/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:101}
vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:101 then
vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:115 sendRequestThroughRouter
vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:84 handle
public/index.php:53 {main}
public/index.php:0 [main]
Even when I remove the Route::group(['middleware' => 'auth'] group from my routes... going in tab 1 to the now open URLs will produce this error. I just don't get it.
How do I get rid of this?
I figured out the cause, but I'm just not sure about it. Hoping one of you will know.
In kernel.php:
I had the 'App\Http\Middleware\VerifyCsrfToken' defined in the $middleware black and not the $routeMiddleware. when I moved it to the $routeMiddleware I stopped getting that error.
Content of VerifyCsrfToken:
class VerifyCsrfToken extends BaseVerifier {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($request && (($this->isReading($request) || $this->excludedRoutes($request) || ($this->tokensMatch($request) && Auth::check()))))
{
return $this->addCookieToResponse($request, $next($request));
}
return view('sessionOver');
}
protected function excludedRoutes($request)
{
$routes = [
'deployPush' // webhook push for bitBucket.
];
foreach($routes as $route)
if ($request->is($route))
return true;
return false;
}
}
Seems to me that the problem is your logic in the check of the CSRF.
Here is the problem:
You are checking if the tokens match, and that function is as follow:
protected function tokensMatch($request)
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
return Str::equals($request->session()->token(), $token);
}
But if you look closely you will see that in the return it checks for:
$request->session()->token()
But, the session is null so you will get an exception for trying to request a method from a null.
So, if I'm not mistaken, all you need to do is just to add an additional check to the if in the handle method.
Instead of this:
if ($request && ((
$this->isReading($request)
|| $this->excludedRoutes($request)
|| ($this->tokensMatch($request) && Auth::check())
)))
You should have this:
if ($request && ((
$this->isReading($request)
|| $this->excludedRoutes($request)
|| ($request->hasSession() && $this->tokensMatch($request) && Auth::check())
)))
This way, if there is no session it will not even check if tokens match and it will not crash there.
You may also consider refactor that part of the if statement to a function that by name it describes what you are checking for, and even the complete if statement to a function that says it all. Just for code clarity.
Is either that, or you may consider checking your other Middlewares, anyone that be touching the headers of the request and potentially be unsetting the headers.
If you take at the code of the handle function of the StartSession Middleware:
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
if ($this->sessionConfigured())
{
$session = $this->startSession($request);
$request->setSession($session);
}
// ** HERE ALL OTHER MIDDLEWARES ARE BEING CALL
$response = $next($request);
if ($this->sessionConfigured())
{
$this->storeCurrentUrl($request, $session);
$this->collectGarbage($session);
// ** AFTER ALL OTHER MIDDLEWARES ARE CALLED THE FOLLOWING FUNCTION
// TOUCHES THE HEADER AND IS NULL
$this->addCookieToResponse($response, $session);
}
return $response;
}
So, it is possible that somewhere in any of any other middleware you are touching the header and leaving it null, as per your error says.
I hope that helps.
May be this will usefull for you .check in middleware some think similar like this
public function handle($request, Closure $next)
{
$response = $next($request);
if ( ! Auth::user()) // Your logic here...
abort(403, 'Unauthorized action.');
return $response;
}
Ref::https://laracasts.com/discuss/channels/general-discussion/disable-global-verifycsrftoken-and-use-as-middleware-doesnt-work-setcookie-on-null
in that discussion some cookie error discussed.that might help you
Experienced a similar issue specifically when returning a response from a middleware, and I realised it was caused by returning return view('my.view.blade') rather than return response()->view('my.view.blade').
So in short, don't return the view directly. Chain the view() helper to the response() helper.
return view('myview.blade'); //wrong
return response()->view('myview.blade'); //works

Laravel 5: force to validate [GET] requests using csrf

by default Laravel 5 validate & match "tokens" for all [POST] requests, how to tell L5 to validate "GET, PUT & Delete" requests too?
-> prevent any request without valid token
thanks,
You can create your own middleware that will take care of it and replace the default Laravel VerifyCsrfToken class. In Laravel 5.3:
Create your new middleware php artisan make:middleware VerifyCsrfTokenAll
Replace the middleware class in app/Http/Kernel.php - search for protected $middlewareGroups and replace VerifyCsrfToken::class by your new middleware. So it can look like this:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfTokenAll::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
...
In app/Http/Middleware/VerifyCsrfTokenAll.php make it extend original verifier and just override the isReading() method as this one is responsible for bypassing the GET requests. Something like this depending on your use case:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfTokenAll extends BaseVerifier
{
/**
* Determine if the HTTP request uses a ‘read’ verb.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function isReading($request)
{
return false;
// return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}
}
If you only wanted to validate in on certain routes, it is better to do it as a route middleware as in my case - I created a VerifyCsrfTokenGet middleware and assigned it in app/Http/Kernel to $routeMiddleware group like this:
protected $routeMiddleware = [
'csrf_get' => \App\Http\Middleware\VerifyCsrfTokenGet::class,
...
In app/Http/MIddleware/VerifyCsrfTokenGet.php I did the verification:
public function handle($request, Closure $next)
{
// check matching token from GET
$sessionToken = $request->session()->token();
$token = $request->input('_token');
if (! is_string($sessionToken) || ! is_string($token) || !hash_equals($sessionToken, $token) ) {
throw new \Exception('CSRF token mismatch exception');
}
return $next($request);
}
and finally assigned this to any route as a csrf_middleware whereever I want to validate it, eg. in constructor of some of the controllers:
class InvoicesController extends Controller
{
function __construct()
{
// define middleware
$this->middleware('csrf_get', ['only' => ['pay', 'createmail']]);
}
"csrf token" is just an ordinary session value with a key name "_token" ,you can just get and reset this value directly.
like this:
$token = $this->request->get('_token');
if(is_null($token) || $token!=csrf_token())
throw new AppException('illegal_pay_operation');
else
Session::regenerateToken();
Laravel validate the token for POST, PUT and DELETE. You don't need to validate the token for a GET request if you follow a RESTful system.
From the documentation:
You do not need to manually verify the CSRF token on POST, PUT, or DELETE requests. The VerifyCsrfToken HTTP middleware will verify token in the request input matches the token stored in the session.
http://laravel.com/docs/5.1/routing#csrf-protection

Can I get current route information in middleware with Lumen?

I need to have the current found controller and action in a middleware, so that I can do some authentication. But I found it impossible, because the pipe is like Middleware1 -> Middleware2-> do the dispatching -> controller#action() -> Middleware2 -> Middleware1.
Therefore before the dispatching, I cannot get the route info. It is definitely not right to do it after the $controller->action().
I did some research and found this.
$allRoutes = $this->app->getRoutes();
$method = \Request::getMethod();
$pathInfo = \Request::getPathInfo();
$currentRoute = $allRoutes[$method.$pathInfo]['action']['uses'];
But this does not work when visiting URI like app/role/1, because $allRoutes only have index of app/role/{id} instead of app/role/1.
Is there any workaround about this?
After do some research, I got solution. Here they go:
Create Custom Dispatcher
First, you have to make your own custom dispatcher, mine is:
App\Dispatcher\GroupCountBased
Stored as:
app/Dispatcher/GroupCountBased.php
Here's the content of GroupCountBased:
<?php namespace App\Dispatcher;
use FastRoute\Dispatcher\GroupCountBased as BaseGroupCountBased;
class GroupCountBased extends BaseGroupCountBased
{
public $current;
protected function dispatchVariableRoute($routeData, $uri) {
foreach ($routeData as $data) {
if (!preg_match($data['regex'], $uri, $matches)) continue;
list($handler, $varNames) = $data['routeMap'][count($matches)];
$vars = [];
$i = 0;
foreach ($varNames as $varName) {
$vars[$varName] = $matches[++$i];
}
// HERE WE SET OUR CURRENT ROUTE INFORMATION
$this->current = [
'handler' => $handler,
'args' => $vars,
];
return [self::FOUND, $handler, $vars];
}
return [self::NOT_FOUND];
}
}
Register Your Custom Dispatcher in Laravel Container
Then, register your own custom dispatcher via singleton() method. Do this after you register all your routes! In my case, I add custom dispatcher in bootstrap/app.php after this line:
require __DIR__.'/../app/Http/routes.php';
This is what it looks like:
/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/
require __DIR__.'/../app/Http/routes.php';
// REGISTER YOUR CUSTOM DISPATCHER IN LARAVEL CONTAINER VIA SINGLETON METHOD
$app->singleton('dispatcher', function () use ($app) {
return FastRoute\simpleDispatcher(function ($r) use ($app) {
foreach ($app->getRoutes() as $route) {
$r->addRoute($route['method'], $route['uri'], $route['action']);
}
}, [
'dispatcher' => 'App\\Dispatcher\\GroupCountBased',
]);
});
// SET YOUR CUSTOM DISPATCHER IN APPLICATION CONTEXT
$app->setDispatcher($app['dispatcher']);
Call In Middleware (UPDATE)
NOTE: I understand it's not elegant, since dispatch called after middleware executed, you must dispatch your dispatcher manually.
In your middleware, inside your handle method, do this:
app('dispatcher')->dispatch($request->getMethod(), $request->getPathInfo());
Example:
public function handle($request, Closure $next)
{
app('dispatcher')->dispatch($request->getMethod(), $request->getPathInfo());
dd(app('dispatcher')->current);
return $next($request);
}
Usage
To get your current route:
app('dispatcher')->current;
I found the correct answer to this problem. I missed one method named routeMiddleware() of Application. This method registers the route-specific middleware which is invoked after dispatching. So Just use $app->routeMiddleware() to register you middleware. And get the matched route info by $request->route() in your middleware.
$methodName = $request->getMethod();
$pathInfo = $request->getPathInfo();
$route = app()->router->getRoutes()[$methodName . $pathInfo]['action']['uses'];
$classNameAndAction = class_basename($route);
$className = explode('#', $classNameAndAction)[0];

Laravel cURL POST Throwing TokenMismatchException

I have a problem with POST cURL request to my application.
Currently, I'm building RESTFUL registration function using laravel 5.
The routes for this is example is
localhost:8000/user/create
I pass value using cURL function on terminal
curl -d 'fname=randy&lname=tan&id_location=1&email=randy#randytan.me&password=randytan&remember_token=Y&created_at=2015-03-03' localhost:8000/auth/register/
And this is my routes.php
Route::post('user/create', 'UserController#create');
And this is my function to store the registration user
public function create()
{
//function to create user.
$userAccounts = new User;
$userAccounts->fname = Request::get('fname');
$userAccounts->lname = Request::get('lname');
$userAccounts->id_location = Request::get('id_location');
$userAccounts->email = Request::get('email');
$userAccounts->password = Hash::make(Request::get('password'));
$userAccounts->created_at = Request::get('created_at');
$userAccounts->save();
return Response::json(array(
'error' => false,
'user' => $userAccounts->fname . " " . $userAccounts->lname
), 200);
}
Executing the cURL syntax above, I'm getting this error TokenMismatchException
Do you have any ideas?
Because I'm implementing middleware only in my few urls, and this cURL registration url is not tight into any authentication mechanism.
Thanks before.
In Laravel 5 (latest version) you can specify routes you want to exclude in /app/Http/Middleware/VerifyCsrfToken.php
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'rest-api/*', # all routes to rest-api will be excluded.
];
}
Hope this helps.
Laravel 5 enforces CSFR token authentication in middleware by default.
you can disable CSFR on selected route Here is the link
or you can try some of these solutions. Hope so it will help.
changing your csfr token method /app/Http/Middleware/VerifyCsrfToken.php
public function handle ($request, Closure $next)
{
if ( !$request->is("api/*"))
{
return parent::handle($request, $next);
}
return $next($request);
}
In my case, i needed to add the route on api.php instead of web.php

Categories