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

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

Related

Exclude route from Slim CSRF middleware

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');

Laravel - Send post data with guzzle

I am trying to send post data and retrieve them in the response.
I look online and found guzzle, so there is what I've done :
The controller part I wan't to call on route 'test' :
public function test(Request $request) {
return $request->input('test');
}
public function sinistre(Client $client) {
$request = $client->post(route('test') , [], [
'form_params' => [
'test' => 'edf'
]
]);
$response = $request->send();
dd($response);
return "ok";
}
Version of guzzle : "guzzlehttp/guzzle": "^6.2"
For now I only got a 500 error response.
Laravel requires a CSRF token to be sent along with the request as it is a post request, so you can either exclude it or get a new token by using csrf_token()
Optionally as per the demo, you can exclude URI's from needing it
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'test/*',
];
}
You would of course need to update the $except URI with yours.

Force logout of specific user by user id in Laravel

I use Laravel 5.2, and I want to know how to force a user to log out by id. I'm building an admin panel with the option to deactivate a specific user that is currently logged in to the web application. Laravel gives you this option for a current user.
Auth::logout()
But I don't want to log out the current user, as I am an authenticated user. So I need to force log out of a specific user by its id. Just like when we log in a user with a specific id.
Auth::loginUsingId($id);
Is there something like the following?
Auth::logoutUsingId($id);
Currently, there's no straightforward way to do this; As the StatefulGuard contract and its SessionGuard implementation don't offer a logoutUsingId() as they do for login.
You need to add a new field to your users table and set it to true when you want a specific user to be logged out. Then use a middleware to check if the current user needs a force logout.
Here's a quick implementation.
1. Add a new field
Let's add a new field to users table migration class:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
// ...
$table->boolean('logout')->default(false);
// other fields...
});
}
// ...
}
Make sure you run php artisan migrate:refresh [--seed] after changing the migration.
2. Force logout middleware
Let's create a new middleware:
php artisan make:middleware LogoutUsers
Here's the logic to check if a user needs to be kicked out:
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
class LogoutUsers
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = Auth::user();
// You might want to create a method on your model to
// prevent direct access to the `logout` property. Something
// like `markedForLogout()` maybe.
if (! empty($user->logout)) {
// Not for the next time!
// Maybe a `unmarkForLogout()` method is appropriate here.
$user->logout = false;
$user->save();
// Log her out
Auth::logout();
return redirect()->route('login');
}
return $next($request);
}
}
3. Register the middleware in HTTP kernel
Open up the app/Http/Kernel.php and add your middleware's FQN:
/**
* The application's route middleware groups.
*
* #var array
*/
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\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LogoutUsers::class, // <= Here
],
'api' => [
'throttle:60,1',
'bindings',
],
];
It's untested code, but it should give you the idea. It'd be a good practice to add a couple of API methods to your User model to accompany with this functionality:
markedForLogout() : Checks user's logout flag.
markForLogout() : Sets user's logout flag to true.
unmarkForLogout() : Sets user's logout flag to false.
Then on the administration side (I suppose it's your case), you just need to call markForLogout() on the specific user model to kick him out on the next request. Or you can utilize the query builder to set the flag, if the model object is not available:
User::where('id', $userId)
->update(['logout' => true]);
It can be a markForLogoutById($id) method.
Related discussions
[Proposal] Log out users by ID
Multiple statements when logged users are deleted
Use the setUser to find a soluion
get current user
$user = Auth::user();
logout user you want to, by id
$userToLogout = User::find(5);
Auth::setUser($userToLogout);
Auth::logout();
set again current user
Auth::setUser($user);
I suggest you to override App\Http\Middleware\Authenticate handle function with custom check
if ($this->auth->user()->deleted_at) {
$this->auth->logout();
return redirect()->route('login');
}
The setup and below code I use it to logout a user and also lock his account(optional)using checklist, I did it through Ajax Request:
-In config/session.php, change to:
'driver' => env('SESSION_DRIVER', 'database'),
-run php artisan session:table
-run php artisan migrate //a session table is created.
-in .env file: change SESSION_DRIVER=file to SESSION_DRIVER=database
Now the coding part:
-In controller:
function ajaxCheckList(Request $request)
{
// \Log::info($request);
$user = \App\Models\User::findOrFail($request['user_id']);
$locked = 0;
if ($user->locked == 1) {
$user->locked = 0;
$locked = 0;
} else {
$user->locked = 1;
$locked = 1;
DB::table('sessions')
->where('user_id', $request['user_id'])
->delete();
}
$user->update(['locked' => $locked]);
}

laravel forcing users to add additional user info

I am quite new to Laravel and I am making a small test application.
But now I am stuck on the following issue.
After a user logged in to the appilcation I want him/her to see a form where he/her have to fill in more information about him/her self before they can continue.
My problem is that I dont know where to put the code for this, I tried placing it in the Controller.php but it does not seem to work (It does sort of only on the main page but not on the profile page), I also tried to put it in the AppServiceProvider.php as my main menu always recives some data from the database but that also didnt seem to work.
Does any one have an idea where to place the following code ?
if (\Request::path() !== 'info' && (\Auth::User()->firstname === NULL || \Auth::User()->lastname === NULL)
{
return \Redirect::to('info');
}
The info page will be the page where the user will see the additional information form.
I think you should create a middleware for that.
First run php artisan make:middleware AccountInfoMiddleware to create the needed file.
Then open app/Http/Middleware/AccountInfoMiddleware.php and add your code to the handle() method:
public function handle($request, Closure $next)
{
$user = \Auth::user();
if($request->path() !== 'info' && $user && ($user->firstname === NULL || $user->lastname === NULL)
{
return redirect('info');
}
return $next($request);
}
After that you have different ways to use your middleware:
Add App\Http\Middleware\AccountInfoMiddleware to the $middleware array in app/Http/Kernel.php. This means the middleware will run for every request.
Add the same thing with a name to the $routeMiddleware in Kernel.php and use it for specific routes or enable it from the controller. For more information, visit the Laravel documentation
If you means you want to redirect user if the user dont have firstname and lastname, You can use middleware to solve your problem. Place your code in middleware file.
Create Middleware file
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Routing\Middleware;
class UserMiddleware implements Middleware {
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (\Request::path() !== 'info' && (\Auth::User()->firstname === NULL || \Auth::User()->lastname === NULL)
{
return \Redirect::to('info');
}
return $next($request);
}
}
Register middleware for route in app/Http/Kernel.php
/**
* The application's global HTTP middleware stack.
*
* #var array
*/
/**
* The application's route middleware.
*
* #var array
*/
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
'user' => 'App\Http\Middleware\UserMiddleware' // Register this new middleware
];
Use that middleware for the route you want
Route::get('/', [
'middleware' => 'user',
'uses' => 'ExampleController#index'
]);
After you add the middleware for routes, every request, handle() function inside middleware file will be run to check if the user have firstname or lastname. If the user dont have, it will redirect to info and if the user have all the info, it will process next request.

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