Keep your forms alive, avoid TokenMismatchException
by gently poking your Laravel app.
Your support allows me to keep this package free, up-to-date and maintainable. Alternatively, you can spread the word!
- Laravel 10 or later.
Require this package into your project using Composer:
composer require laragear/poke
This package pokes your App with an HTTP HEAD
request to the /poke
route at given intervals. In return, while your application renews the session lifetime, it returns an HTTP 204
status code, which is an OK Response without body.
This amounts to barely 0.8 KB sent!
The Poke script will detect if the CSRF session token is expired based on the last successful poke, and forcefully reload the page if there is Internet connection.
This is done by detecting when the browser or tab becomes active, or when the device user becomes online again.
This is handy in situations when the user laptop is put to sleep, or the phone loses signal. Because the session may expire during these moments, the page is reloaded to get the new CSRF token when the browser wakes up or the phone becomes online.
There are three ways to turn on Poke in your app.
auto
(easy hands-off default)middleware
blade
(best performance)
You can change the default mode using your environment file:
POKE_MODE=auto
Just install this package and look at it go. This will append a middleware in the web
group that will look into all your Responses content where:
- the request accepts HTML, and
- an input with
csrf
token is present.
If there is any match, this will inject the Poke script in charge to keep the forms alive just before the </body>
tag.
This mode won't inject the script on error responses or redirections.
Note
It's recommended to use the other modes if your application has many routes or Responses with a lot of text.
This mode does not push the middleware to the web
group. Instead, it allows you to use the poke
middleware only in the routes you decide.
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\RegisterController;
Route::get('register', RegisterController::class)->middleware('poke');
This will inject the script into the route response if there is an input with a CSRF token. You can also apply this to a route group.
You may want to use the force
option to forcefully inject the script at the end of the <body>
tag, regardless of the CSRF token input presence. This may be handy when you expect to dynamically load forms on a view after its loaded, or SPA.
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StatusController;
Route::get('status', StatusController::class)->middleware('poke:force');
As with auto
mode, this mode won't inject the script on errors or redirections.
The blade
mode disables middleware injection, so you can use the <x-poke-script />
component freely to inject the script anywhere in your view, preferably before the closing </body>
tag.
<body>
<h2>Try to Login:</h2>
<form action="/login" method="post">
@csrf
<input type="text" name="username" required>
<input type="password" name="password" required>
<button type="submit">Log me in!</button>
</form>
<x-poke-script /> <!-- This is a good place to put it -->
</body>
This may be useful if you have large responses, like blog posts, articles or galleries, since the framework won't spend resources inspecting the response, but just rendering the component.
Tip
Don't worry if you have duplicate Poke components in your view. The script is rendered only once, and even if not, the script only runs once.
For fine-tuning, you can publish the poke.php
config file.
php artisan vendor:publish --provider="Laragear\Poke\PokeServiceProvider" --tag="config"
Let's examine the configuration array:
return [
'mode' => env('POKE_MODE', 'auto'),
'times' => 4,
'poking' => [
'route' => 'poke',
'name' => 'poke',
'domain' => null,
'middleware' => 'web',
]
];
How many times the poking will be done relative to the global session lifetime. The more times, the shorter the poking interval. The default 4
should be fine for any normal application.
For example, if our session lifetime is the default of 120 minutes:
- 3 times will poke the application each 40 minutes,
- 4 times will poke the application each 30 minutes,
- 5 times will poke the application each 24 minutes,
- 6 times will poke the application each 20 minutes, and so on...
In other words, session lifetime / times = poking interval
.
- 🔺 Raise the intervals if you expect users idling in your site for several minutes, even hours.
- 🔻 Lower the intervals if you expect users with a lot of activity.
This is the array of settings for the poking route which receives the Poke script request.
return [
'poking' => [
'route' => 'poke',
'name' => 'poke',
'domain' => null,
'middleware' => ['web'],
]
];
The route (relative to the root URL of your application) that will be using to receive the pokes.
return [
'poking' => [
'route' => '/dont-sleep'
],
];
Note
The poke routes are registered at boot time.
Name of the route, to find the poke route in your app for whatever reason.
return [
'poking' => [
'name' => 'my-custom-poking-route'
],
];
The Poke route is available on all domains. Setting a given domain will scope the route to that domain.
In case you are using a domain or domain pattern, it may be convenient to put the Poke route under a certain one. A classic example is to make the poking available at http://user.myapp.com/poke
but no http://api.myapp.com/poke
.
return [
'poking' => [
'domain' => '{user}.myapp.com'
],
];
The default Poke route uses the web
middleware group to function properly, as this group handles session, cookies and CSRF tokens.
You can add your own middleware here if you need to.
return [
'poking' => [
'middleware' => ['web', 'validates-ip', 'my-custom-middleware']
],
];
You can also use the "bare minimum" middleware if you feel like it, thus it may be problematic if you don't know what you're doing.
return [
'poking' => [
'middleware' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
],
]
]
Poke injects the script as a Blade component at all times.
You can override the script by publishing it under the views
tag:
php artisan vendor:publish --provider="Laragear\Poke\PokeServiceProvider" --tag="views"
Some people may want to change the script to use a custom Javascript HTTP library, minify the response, make it compatible for older browsers, or even create a custom Event when CSRF token expires.
The view receives three variables:
$route
: The relative route where the poking will be done.$interval
: The interval in milliseconds the poking should be done.$lifetime
: The session lifetime in milliseconds.
- Only the
InjectsScript
middleware is bound as a singleton, and it saves the mode, which will persist across all the process lifetime. The mode is not intended to change request-by-request.
There should be no problems using this package with Laravel Octane.
If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
This specific package version is licensed under the terms of the MIT License, at time of publishing.
Laravel is a Trademark of Taylor Otwell. Copyright © 2011-2024 Laravel LLC.