Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logging out asynchronously keeps returning users data, until you refresh page #85

Closed
jonasvanderhaegen opened this issue Sep 14, 2016 · 10 comments

Comments

@jonasvanderhaegen
Copy link

It's more of a detail than issue maybe but nevertheless I'll just address it here.
(experience with L5.3 & vue/vue-resource/vue-router is good).

  1. In vue-router with router.beforeEach there's an ajax call to /api/user
  2. get Unauthenticated and 401 which is good.
  3. I log in asynchronously, getting my user data switching pages, also good.
  4. Then I log out, I like things to be asynchronous so I log out via ajax post request.
  5. I switch pages and notice I'm still logged in.
  6. until I refresh then I'm logged out and getting nr. 2, which I would prefer to have without having to refresh the page.

In the meanwhile I'll try to figure it out myself. While yes I could refresh the page with javascript or whatever I think this would be a nice detail to not have to refresh page to actually be logged out with one page applications.

@SebastianS90
Copy link
Contributor

You are probably using the CreateFreshApiToken middleware. It sets a JWT cookie named laravel_token.
That cookie is signed and encrypted. It contains the user id, an expiration time, and the CSRF token.
Everyone who has such a cookie and the corresponding CSRF token is authenticated with the contained user id, you cannot revoke that. But of course the cookie is no longer valid after the (signed) expiration time.

If you do not delete that cookie then the user can still access the API. A page reload will start a new session with a different CSRF-token, but if you stay within your loaded single page application then you still use the old CSRF token that is the one needed to use the laravel_token cookie.

Try adding this to your logout function:

return /*whatever response you return*/->withCookie(Cookie::forget('laravel_token'))

@coolynx
Copy link

coolynx commented Oct 23, 2016

@jonasvanderhaegen you can get 401 error without refreshing the page - log in back asynchronously and you have it.

@SebastianS90 suggestion works, using Laravel Facade cookie.

return /*whatever response you return*/->withCookie(cookie()->forget('laravel_token'))

@vasilegoian
Copy link

This is a serious problem. Let's say I have a SPA and I have two tabs opened on the browser. I logout in one of them and step away from the computer. This basically provides full access to anyone who sits next to me and doesn't refresh the other tab.

@SebastianS90
Copy link
Contributor

I agree. The solution is to delete the cookie on logout. I see two approaches:

  • Check here whether the laravel_token cookie is set. If so, delete that cookie. That is not beautiful because we have to change the framework instead of just the passport package. And we also should consider a custom name for the cookie (see Allow to configure ApiTokenCookie name (2) #142).
  • Let the passport package register an event listener for the Logout Event and delete the cookie in there. I guess that would be a cleaner solution.

@vasilegoian
Copy link

vasilegoian commented Oct 27, 2016

@SebastianS90 deleting the cookie on one tab, does not solve the issue with it being sent from another already opened tab.

One possible solution would be to generate a "session" for the user and when you logout, deleting that session. But that session should be stored on the server and compared at each request. That session should be created at login and destroyed at logout and to work together with the current cookie (which is for guests too). So there should be 3 auth middlewares, web, api and spa.

@SebastianS90
Copy link
Contributor

@doublebit How does your second tab still work when the cookie is deleted? The requests sent from the second tab after proper logout in the first tab (with deleting that cookie) won't have the token cookie any more and therefore they will not be authenticated. The cookies are per-browser and not per-tab.
Just try it by deleting the cookie using chrome developer tools (or whatever your browser offers) while the SPA is still open.

@vasilegoian
Copy link

@SebastianS90 How do you delete a cookie in a tab that's already opened? Steps:

  1. Open tab A.
  2. Authenticate
  3. Open tab B and navigate to the same page.
  4. Switch back to tab A and logout.

At this point, if you switch back to tab B, it still has the cookie. It was deleted only on tab A.

The cookie, as you know, is stored on the browser, not on the server, and deleting it in one request, does nothing more than telling that tab of the browser to delete it, but it doesn't affect other tabs already opened.

@SebastianS90
Copy link
Contributor

I tried it, and it woks for me as expected when using my custom logout controller function:

    public function logout(Request $request)
    {
        Auth::logout();

        // Delete all session data and get a new
        // session id for security
        $request->session()->flush();
        $request->session()->regenerate();

        // Go back to login page
        return redirect()->route('login')

            // Delete the passport authentication token
            ->withCookie(Cookie::forget(Passport::cookie()));
    }

The last line is the most important one. Don't forget to import (use) the Illuminate\Support\Facades\Cookie and Laravel\Passport\Passport classes into your namespace.

After authentication, the server sends Set-Cookie:laravel_token=[encrypted JWT token]; expires=Thu, 27-Oct-2016 16:10:00 GMT; Max-Age=7200; path=/; HttpOnly

All following requests (in particular the ajax requests to my API) carry the header Cookie:[...]; laravel_token=[encrypted JWT token]. They also have a X-CSRF-TOKEN:[token] header which was set by vue-resource.

On logout, the server sends: Set-Cookie:laravel_token=[the literal string 'deleted', but encrypted by the EncryptCookies middleware]; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; HttpOnly

When I go back to my other tab and issue some ajax requests, then they do not carry the laravel_token in its cookie header. The X-CSRF-TOKEN header is still present as that is something which is stored within the page. But without the lravel_token, the request is not authenticated and I get a 401 error code.

Even if I login again in a different tab, then I cannot use the tab that is still open from the first session. The reason for this is that we again send a laravel_token cookie along with the requests, but the old X-CSRF-TOKEN does not match the one that is encoded within the new JWT token cookie. The line $request->session()->flush(); ensures that the new session has a different csrf token.

Note that cookies are stored in the browser, not in the page or tab. So modifying cookies from one tab also affects your other tabs. Therefore the logout works. On the other hand, the csrf token is stored within the page/tab. You need both in order to be logged in, i.e. a valid laravel_token cookie and the corresponding csrf token in the request header. Requests from other websites (attackers) won't know the csrf token, requests from your old tabs after logout won't carry the cookie.

The real issue here is that the default implementation does not delete the laravel_token cookie on logout, I had to do that myself (using a custom logout controller function).

@vasilegoian
Copy link

@SebastianS90 Thanks man! You're right, the cookie is not stored in the tab, that's what I was omitting. The default implementation of logout is fine, but the documentation for passport should mention your solution. Maybe you can create a pull request on docs to update this section with your solution.

@SebastianS90
Copy link
Contributor

I've chosen to post a pull request to handle it out of the box. Lazy developers don't read all documentation and might oversee this tiny but very important detail. And without this precaution it is really easy for an attacker to get back to a working session by using Ctrl+Alt+T or just the browser's back button.

Thanks @doublebit for pointing out the potential security issue with this. I hope they accept my PR or something similar to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants