'419 error in Laravel after logging out and back in (multiple tabs)

I have two tabs open in my Laravel application.

I click "logout" in one tab. Then I click "logout" in the second tab. This second logout used to give a 419 error, but I added it to the exclusion list in the VerifyCsrfToken middleware. I don't see why I would need CSRF protection for logging out.

But, now I have a different issue.

After logging out in both tabs, both of them are sitting on the login page. Now if I try to log back in from the first tab, I get a 419 error. From the second tab, the login works correctly.

How can I handle this? I don't want to show an error to the user when they click "login", it's bad user experience. I also don't want to exclude the login route from CSRF protection.



Solution 1:[1]

Considerations

in this Answer i am naming your browser opened tab as Tab A and Tab B

just consider this following rules:

  • your browser have one session active for a domain at a time (access with session_id cookie).
  • client-side : every opened tab can have a CSRF Token in its forms,header,....
  • server-side : only one csrf token are held in session variable.
  • on every post request, the client and server token should be match, else a 419 error rises.

Necessity of CSRF Protection for logout

no there is no need for preventing CSRF Protection in logout route, since its not a Resource changing route. and it was good that you except=['logout'] from CSRF Protection.

Happening Scenario

1

first you have opened tab A and B they are logged in and shared same session and token

tab A - session 1 - token x tab B - session 1 - token x

2

click on tab A logout. it will cleared session from server . redirect back to login page. in login page, client request to server, made him a new session and token.

tab A - session 2 - token y tab B - session 2 (cookie with session_id are for domain) - token x (client: is presented in tab / removed from server because it was in session 1)

since there is no session 1 in server-side which holds token x so the tab B token mismatched the server y token

3

sending logout on tab B .

if you send logout with csrf enabled. middleware sends you 419 because there is mistmached between token x on client anb b on server .

if its Exempt from csrf , it **clears session ** on server side and redirect client to login page, after redirection server make a new session with a new csrf token for tab B.

i suppose you exempt csrf protection on logout route and continue with second scenario.

tab A - session 3 - token y tab B - session 3 - token z

as you see server has session 3 with token z . so tab A mistmatched

Conclusion

as you saw there was some reasons for this mistmatches:

  • session (cookie) are browser scope but tokens are tab scope
  • user do something on a tab that make other tab to have a session which mistmaches its form token

so as you see The Prevention Solution has get appeared :

if users want to:

  • have multiple tab opend
  • Logout (re-creating session) on one tab
  • continue with other opened tab/forms (a refresh could solve the problem)

Solution

you have to make The Session and token Scope Sync

there are 2 solutions :

  1. make token browser-scope : with using XSRF instead of CSRF which achievable with using SPA client side frameworks. (preferable)

  2. make session page-scope :

    • change logout functionality to not to clear session just clear the response session_id

    • make session tracking with hidden input (which has its downwards itself) (obsolote)

I Recommend to use Single Page Application Frameworks for Client Side

Solution 2:[2]

Simple and easy Solution to avoid from "419 page expired":

  1. Go to middleware.
  2. VerifyCsrfToken.php
  3. Create new method handle
    use Closure;

    use Illuminate\Support\Facades\Auth;

    public function handle($request, Closure $next)
    {
        if($request->route()->named('logout')) {
            if (!Auth::check() || Auth::guard()->viaRemember()) {
                $this->except[] = route('logout');
            }   
        }

      return parent::handle($request, $next);
   }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Abilogos
Solution 2 ouflak