Last active
August 19, 2024 10:37
-
-
Save jrmadsen67/bd0f9ad0ef1ed6bb594e to your computer and use it in GitHub Desktop.
Laravel Quick Tip: Handling CsrfToken Expiration gracefully
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Quick tip for handling CSRF Token Expiration - common issue is when you use csrf protection is that if | |
a form sits there for a while (like a login form, but any the same) the csrf token in the form will | |
expire & throw a strange error. | |
Handling it is simple, and is a good lesson for dealing with other types of errors in a custom manner. | |
In Middleware you will see a file VerifyCsrfToken.php and be tempted to handle things there. DON'T! | |
Instead, look at your app/Exceptions/Handler.php, at the render($request, Exception $e) function. | |
All of your exceptions go through here, unless you have excluded them in the $dontReport array at the | |
top of this file. You can see we have the $request and also the Exception that was thrown. | |
Take a quick look at the parent of VerifyCsrfToken - Illuminate\Foundation\Http\Middleware\VerifyCsrfToken. | |
You can see from VerifyCsrfToken.php that handle() is the function called to do the token check. In the | |
parent class, if the token fails, a `TokenMismatchException` is thrown. | |
So back in the Handler class, let's specifically handle that type of exception: | |
public function render($request, Exception $e) | |
{ | |
if ($e instanceof \Illuminate\Session\TokenMismatchException) | |
{ | |
return redirect() | |
->back() | |
->withInput($request->except('password')) | |
->with([ | |
'message' => 'Validation Token was expired. Please try again', | |
'message-type' => 'danger']); | |
} | |
return parent::render($request, $e); | |
} | |
The code is simple - if the exception is a `TokenMismatchException` we will handle it just like | |
a validation error in a controller. In our forms(s), we need to be sure to use the | |
$request->old('field_name') (or the old('field_name') helper function) to repopulate. Simply going | |
"back" will refresh the form with a new token so they can re-submit. | |
CAREFUL! - I found that using the http://laravelcollective.com/ Form::open() tag seemed to be | |
incompatible with the token - redirect()->back() was not refresh the token for me. This may just be | |
something in my code, but when I used a regular html tag it was fine. If this is happening to you, | |
try that. | |
does it works in Laravel 8?
This is great, thank you.
Regarding how to handle AJAX requests, here's what I did.
-
First, add the code above to
Handler.php
, so normal requests are handled gracefully. -
Next, tweak that code a bit to ignore AJAX requests. If it is an AJAX request, we don't want to redirect back with an error, so exclude them like so:
if ($e instanceof \Illuminate\Session\TokenMismatchException && ! $request->expectsJson()) {
-
Now AJAX requests will throw an exception just as before, and we need to handle those in JS. I'm using jQuery, so for eg with
$.post()
:$.post(...) .fail(function xhr, status, error) { if (xhr.status === 419) { // Token error - handle however you want alert('The page has expired due to inactivity. Please refresh and try again'); } });
does it works in Laravel 8?
@dagpacket you can try this code, it woks on 8
use Exception;
. . .
public function register() {
$this->reportable(function (Throwable $e) {
// ..
});
$this->renderable(function (Exception $e, $request) {
if ($e->getPrevious() instanceof \Illuminate\Session\TokenMismatchException) {
return redirect()
->back()
->withInput($request->except('password', 'password_confirmation', '_token'))
->withErrors(['Your form has expired, please try again']);
}
});
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have a REST/single page application and found this. For the ajax questions, this worked for me... Not super graceful, but it does the trick.
`
public function render($request, Exception $e)
{
`
Of course, your code that handles 302 in an ajax request needs to listen for this and manually redirect... location.href = "/" or something similar, but it works.