Skip to content
Advertisement

How can I apply atomic locks to all of my routes in Laravel?

I have a single page application which is computing multiple AJAX requests, via axios. Occasionally, I run into a scenario where both requests run async at the same time and attempt to write to the session which causes a 419 response code where the CSRF token is dropped in the response forcing me to have to logout the user.

After debugging this, I come across session blocking which makes use of atomic locks.

My session provider is the database and I have the standard Schema that ships with Laravel for this capability. Currently, I must specify this on my routes like so:

Route::any('/example', [SomeController::class, 'someFunction')->block();

However, I have a mass amount of routes and since the application is SPA, is there a way to make this happen by default at service provider level?

It is not possible to Route::group(...)->block() which is what I did originally try.

Advertisement

Answer

Achieving this is a bit of a pain. But before I present a solution, a short analysis:

The Route::block($lockSeconds = 10, $waitSeconds = 10) method internally sets the attributes lockSeconds and waitSeconds on the Route. If these two attributes are added to a group definition, they are actually passed to the route, but unfortunately under the sub key action:

Route::group(['lockSeconds' => 10, 'waitSeconds' => 10, 'prefix' => 'api/spa'], function () {
    Route::get('foo', fn () => response()->json(['result' => 'ok']));
});

// Dump of the route
"api/spa/foo" => IlluminateRoutingRoute {#217 ▼
  +uri: "api/spa/foo"
  +methods: array:2 [▶]
  +action: array:5 [▼
    "middleware" => array:1 [▶]
    "uses" => Closure() {#216 ▶}
    "namespace" => null
    "prefix" => ""
    "where" => [],
    "lockSeconds" => 10,             <-- we got the setting here
    "waitSeconds" => 10              <--
  ]
  +isFallback: false
  +controller: null
  +defaults: []
  +wheres: []
  +parameters: null
  +parameterNames: null
  #originalParameters: null
  #withTrashedBindings: false
  #lockSeconds: null                 <-- we want the setting here though
  #waitSeconds: null                 <--
  +computedMiddleware: null
  +compiled: null
  #router: IlluminateRoutingRouter {#25 ▶}
  #container: IlluminateFoundationApplication {#3 ▶}
  #bindingFields: []
}

That leaves us with the situation that defining the two attributes during route registration is not possible. But fortunately, we can also manipulate routes after they’ve been added to the route collection:

foreach (Route::getRoutes() as $route) {
    $route->block();
}

This code is best placed at the bottom of your routes/web.php or at the end of the map() method in your RouteServiceProvider. You also may want to filter your routes when doing this and only update routes starting with a given prefix:

foreach (Route::getRoutes() as $route) {
    if (str_starts_with($route->uri(), 'api/spa')) {
        $route->block();
    }
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement