Handling expired token in Laravel

I combine 2 things for this case:

1. Increase session lifetime

//In config/session.php replace this:

'lifetime' => 120

//with:

'lifetime' => 360

Laravel 5 default lifetime is 120 (minutes), you can change it to whatever value you like, for example 360 (6 hours)

2. Catch the exception and display an error message

//In app/Exceptions/Handler.php replace this:

public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        $e = new NotFoundHttpException($e->getMessage(), $e);
    }

    return parent::render($request, $e);
}

//with:

public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        $e = new NotFoundHttpException($e->getMessage(), $e);
    }

    if ($e instanceof \Illuminate\Session\TokenMismatchException) {            
        return redirect('/')->withErrors(['token_error' => 'Sorry, your session seems to have expired. Please try again.']);
    }

    return parent::render($request, $e);
}

So basicaly you redirect the user to the root "/" (you can change this to any path you want) with an error message and on that page you have to do this to display the error message:

@if ($errors->has('token_error'))
    {{ $errors->first('token_error') }}
@endif

I think the answer by @UX Labs is misleading. And then the comment from @jfadich seems completely incorrect.

For Laravel 5.4 in May 2017, I solved the problem this way:

Here Is an Answer That Works

In web.php:

Route::post('keep-token-alive', function() {
    return 'Token must have been valid, and the session expiration has been extended.'; //https://stackoverflow.com/q/31449434/470749
});

In javascript in your view:

$(document).ready(function () {

    setInterval(keepTokenAlive, 1000 * 60 * 15); // every 15 mins

    function keepTokenAlive() {
        $.ajax({
            url: '/keep-token-alive', //https://stackoverflow.com/q/31449434/470749
            method: 'post',
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            }
        }).then(function (result) {
            console.log(new Date() + ' ' + result + ' ' + $('meta[name="csrf-token"]').attr('content'));
        });
    }

});

Note that you must not list 'keep-token-alive' in the exclusions within VerifyCsrfToken.php. As @ITDesigns.eu implied in a comment, it's important for this route to verify that there is a valid token currently and that it just needs to have its expiration extended.

Why this approach solves my problem

My Laravel site allows users to watch a video (an hour long), and it uses ajax to post their progress every minute.

But many users load the page and then don't start the video until many hours later.

I don't know why they leave their browser tab open so long before watching, but they do.

And then I'd get a ton of TokenMismatch exceptions in my logs (and would miss out on the data of their progress).

In session.php, I changed 'lifetime' from 120 to 360 minutes, but that still wasn't enough. And I didn't want to make it longer than 6 hours. So I needed to enable this one page to frequently extend the session via ajax.

How you can test it and get a sense for how the tokens work:

In web.php:

Route::post('refresh-csrf', function() {//Note: as I mentioned in my answer, I think this approach from @UX Labs does not make sense, but I first wanted to design a test view that used buttons to ping different URLs to understand how tokens work. The "return csrf_token();" does not even seem to get used.
    return csrf_token();
});
Route::post('test-csrf', function() {
    return 'Token must have been valid.';
});

In javascript in your view:

<button id="tryPost">Try posting to db</button>
<button id="getNewToken">Get new token</button>

(function () {
    var $ = require("jquery");

    $(document).ready(function () {
        $('body').prepend('<div>' + new Date() + ' Current token is: ' + $('meta[name="csrf-token"]').attr('content') + '</div>');
        $('#getNewToken').click(function () {
            $.ajax({
                url: '/refresh-csrf',
                method: 'post',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            }).then(function (d) {
                $('meta[name="csrf-token"]').attr('content', d);
                $('body').prepend('<div>' + new Date() + ' Refreshed token is: ' + $('meta[name="csrf-token"]').attr('content') + '</div>');
            });
        });
        $('#tryPost').click(function () {
            $.ajax({
                url: '/test-csrf',
                method: 'post',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            }).then(function (d) {
                $('body').prepend('<div>' + new Date() + ' Result of test: ' + d + '</div>');
            });
        });


    });
})();

In session.php, temporarily change 'lifetime' to something very short for testing purposes.

Then play around.

This is how I learned how the Laravel token works and how we really just need to successfully POST to a CSRF-protected route frequently so that the token continues to be valid.


Update 2022; the csrf_token() method will never create a new token, and it simply loads existing CSRF-token from current-session (if any, and returns it).

But this tricks you into thinking it works, because Laravel increases the life-time of the existing CSRF-token, and that each time a request to a CSRF-protected route is made.

For an implemention that really creates new CSRF-token, see:

stackoverflow.com/Get new CSRF token with Ajax?

Original Answer (From 2015)

A work around for it, is to actually get the new token every certain time, otherwise you are defeating the purpose of the csrf token:

<html>
    <head>
        <meta name="csrf_token" content="{{ csrf_token() }}">
    </head>
    <body>
        <script type="text/javascript">
            var csrfToken = $('[name="csrf_token"]').attr('content');
            
            setInterval(refreshToken, 3600000); // 1 hour 
            
            function refreshToken(){
                $.get('refresh-csrf').done(function(data){
                    csrfToken = data; // the new token
                });
            }

            setInterval(refreshToken, 3600000); // 1 hour 

        </script>
    </body>
</html>

In laravel routes

Route::get('refresh-csrf', function(){
    return csrf_token();
});

I apologize in case of any syntax errors, haven't used jquery for long time, but i guess you get the idea