Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

laravel csrf token mismatch exception after session timeout

in our laravel 5 app, the login is through ajax. if user logout and log back in before session expires, everything is fine. but if user logout and stay idle on that page until session is expired, user will get a csrfTokenMismatch exception if they attempt to log back in.

i know in verifyCsrfToken middleware, laravel checks if session matches with the csrf token. also in Guard.php logout() method, session will be cleared on logout.

so my questions are:

is session really flushed on logout, if so how come user can still log back in before the session i set expires?

what happens to csrf token when session is expired?

and lastly, how is this issue usually handled in an elegant way?

Thanks in advance!

like image 273
shangsunset Avatar asked Jul 03 '15 15:07

shangsunset


2 Answers

This answer is in reference to version 5.4, perhaps previous versions but I haven't tested those.

The root of the problem is the CSRF token is expired on the client side which makes any POST to the server fail with that token.

IF you're using AJAX, you could use the API routes which do not do CSRF verification by default.

You could turn off CSRF verification for specific URIs. In this case, I'm turning off CSRF verification for /logout. This approach works well if you truly want to exclude certain URIs from verification.

app/Http/Middleware/VerifyCsrfToken.php

/**
 * The URIs that should be excluded from CSRF verification.
 *
 * @var array
 */
protected $except = [
   '/logout'
];

In addition, you should handle the CSRF error when it occurs in a way that works well for your application. Below is an example of something very basic you could do.

app/Exceptions/Handler.php

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if($exception instanceof \Illuminate\Session\TokenMismatchException){
        // token mismatch is a security concern, ensure logout.
        Auth::logout();

        // Tell the user what happened.
        session()->flash('alert-warning','Your session expired. Please login to continue.');

        // Go to login.
        return redirect()->route('login');
     }

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

By the way, to test both of these changes, you can modify the session settings. I just set the lifetime to 1 for testing. Then, set it back when you're finished (it is 120 by default). You'll want to login, load your form page, wait over a minute, then attempt your POST.

config/session.php

    /*
    |--------------------------------------------------------------------------
    | Session Lifetime
    |--------------------------------------------------------------------------
    |
    | Here you may specify the number of minutes that you wish the session
    | to be allowed to remain idle before it expires. If you want them
    | to immediately expire on the browser closing, set that option.
    |
    */

    'lifetime' => 1,

like image 160
JR Lawhorne Avatar answered Sep 28 '22 21:09

JR Lawhorne


There is a cookie with name XSRF-Token, that has a default live-time of 2 hours...

I handled TokenMissmatchExceptions on login forms like this by modifying App/Exceptions/Handler.php :

// ....
use Illuminate\Session\TokenMismatchException;
// ....


public function render($request, Exception $e)
{
    if($e instanceof TokenMismatchException) {
        $uri = \Route::current()->uri();
        if($uri == "login") {
            return redirect()->route('your.login.route')
                             ->withErrors("Login Form was open too long. 
                                           Please try to login again");
        }
    }
    return parent::render($request, $e);
}
like image 28
shock_gone_wild Avatar answered Sep 28 '22 20:09

shock_gone_wild