Programmatically add an exception to the CSRF check from the Laravel package

The problem in a nutshell

I am looking for a way to remove VerifyCsrfTokenfrom a global middleware pipeline from within a package without user modification App\Http\Middleware\VerifyCsrfToken. Is it possible?

Usage example

I am developing a package that will facilitate the safe addition of push-to-deploy functions to any Laravel project. I start with github. Github uses webhooks to notify third-party applications of events such as clicks or releases. In other words, I would register a URL, such as http://myapp.com/deploy on Github, and Github would send a request POSTto this URL with useful information containing information about the event when this occurs, and I could use this event to start a new deployment. Obviously, I don’t want to start the deployment on time if some random (or possibly malicious) agent other than the Github service gets to this URL. This way, Github has a process to protect your web hosts.. This includes registering a private key with Github, which they will use to send a special, secure hashed header along with the request, which you can use to verify it.

My approach to security includes:

Random Unique URL / Route and Secret Key

First, I automatically generate two random unique strings that are stored in a file .envand are used to create a private key route in my application. In the file, .envit looks like this:

AUTODEPLOY_SECRET=BHBfCiC0bjIDCAGH2I54JACwKNrC2dqn
AUTODEPLOY_ROUTE=UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx

configfor this package creates two keys auto-deploy.secretand auto-deploy.routewhich I can get when registering a route so that it never gets published in any repo:

Route::post(config('auto-deploy.route'),'MyController@index');

Then I can go to Github and register my website as follows:

Github Web Host Registration Screen

, URL- , , , .

Webhook

Laravel, webhook. , , , Laracasts. ServiceProvider :

public function boot(Illuminate\Contracts\Http\Kernel $kernel)
{
    // register the middleware
    $kernel->prependMiddleware(Middleware\VerifyWebhookRequest::class);
    // load my route
    include __DIR__.'/routes.php';
}

Route :

Route::post(
    config('auto-deploy.route'), [
        'as' => 'autodeployroute',
        'uses' => 'MyPackage\AutoDeploy\Controllers\DeployController@index',
    ]
);

handle(), :

public function handle($request, Closure $next)
{
    if ($request->path() === config('auto-deploy.route')) {
        if ($request->secure()) {
            // handle authenticating webhook request
            if (/* webhook request is authentic */) {
                // continue on to controller
                return $next($request);
            } else {
                // abort if not authenticated
                abort(403);
            }
        } else {
            // request NOT submitted via HTTPS
            abort(403);
        }
    }
    // Passthrough if it not our secret route
    return $next($request);
}

continue on to controller.

, , POST, session() CSRF , VerifyCsrfToken TokenMismatchException . , - VerifyCsrfToken . , .

# 1: VerifyCsrfToken

URL- $except App\Http\Middleware\VerifyCsrfToken,

// The URIs that should be excluded from CSRF verification
protected $except = [
    'UG2Yu8QzHY6KbxvLNxcRs0HVy9lQnKsx',
];

, , , , , . , :

protected $except = [
    config('auto-deploy.route'),
];

PHP . :

protected $except = [
    'autodeployroute',
];

. URL. , , - :

protected $except = [];

public function __construct(\Illuminate\Contracts\Encryption\Encrypter $encrypter)
{
    parent::__construct($encrypter);
    $this->except[] = config('auto-deploy.route');
}

Laravel. , , , , -, . , , , , , , Laravel, .

# 2: catch TokenMismatchException

, , , , , ..:

public function handle($request, Closure $next)
{
    if ($request->secure() && $request->path() === config('auto-deploy.route')) {
        if ($request->secure()) {
            // handle authenticating webhook request
            if (/* webhook request is authentic */) {
                // try to continue on to controller
                try {
                    // this will eventually trigger the CSRF verification
                    $response = $next($request);
                } catch (TokenMismatchException $e) {
                    // but, maybe we can just ignore it and move on...
                    return $response;
                }
            } else {
                // abort if not authenticated
                abort(403);
            }
        } else {
            // request NOT submitted via HTTPS
            abort(403);
        }
    }
    // Passthrough if it not our secret route
    return $next($request);
}

, . wabbit, , try/catch ! , $response - undefined catch. $next($request) catch, TokenMismatchException.

# 3:

, Controller handle(). , . , - , , Laravel , . , , .

# 4: Pipeline

, Pipeline Laravel. Laravel . , , , Pipeline, , CSRF . , , , - . , , !

# 5: WithoutMiddleware

, , , , , . , , , . , .

№6: . Forge Envoyer

? , push-to-deploy, , ? , , 5 , - 5 10 . , . , , , , , .

, , , . ? , , .

№1: Laravel , !

, , . "" " ---", , , ., , , " " , . , .

№ 2: ,

, - , . , ; , . , , : " !" ?

, :

  • , ?
  • ? ?

, .

P.S. - , , , , , .

+2
2

, API Reflection , , , . , .

, .

tl; dr - :

// Just remove CSRF middleware when we hit the deploy route
if(request()->is(config('auto-deploy.route')))
{
    // Create a reflection object of the app instance
    $appReflector = new ReflectionObject(app());

    // When dumping the App instance, it turns out that the
    // global middleware is registered at:
    // Application
    //  -> instances
    //   -> Illuminate\Contracts\Http\Kernel
    //    -> ... Somewhere in the 'middleware' array
    //
    // The 'instance' property of the App object is not accessible
    // by default, so we have to make it accessible in order to
    // get and set its value.
    $instancesProperty = $appReflector->getProperty('instances');
    $instancesProperty->setAccessible(true);
    $instances = $instancesProperty->getValue(app());
    $kernel = $instances['Illuminate\Contracts\Http\Kernel'];

    // Now we got the Kernel instance.
    // Again, we have to set the accessibility of the instance.
    $kernelReflector = new ReflectionObject($kernel);
    $middlewareProperty = $kernelReflector->getProperty('middleware');
    $middlewareProperty->setAccessible(true);
    $middlewareArray = $middlewareProperty->getValue($kernel);

    // The $middlewareArray contains all global middleware.
    // We search for the CSRF entry and remove it if it exists.
    foreach ($middlewareArray as $i => $middleware)
    {
        if ($middleware == 'App\Http\Middleware\VerifyCsrfToken')
        {
            unset($middlewareArray[ $i ]);
            break;
        }
    }

    // The last thing we have to do is to update the altered
    // middleware array on the Kernel instance.
    $middlewareProperty->setValue($kernel, $middlewareArray);
}
+2

Laravel 5.1 - 5.2 .

, Route::group, , .

, ServiceProvider - :

    \Route::group([
        'middleware' => ['only-middleware-you-need']
    ], function () {
        require __DIR__ . '/routes.php';
    });

VerifyCsrfToken , .

0

Source: https://habr.com/ru/post/1611988/


All Articles