huwcarwyn / react-laravel-boilerplate

A Laravel REST API backend with React/Redux, hot module reloading in development and route-level code splitting
MIT License
172 stars 43 forks source link

How to revoke tokens in session for user? #12

Closed MovingGifts closed 5 years ago

MovingGifts commented 5 years ago

Hello again, I ran into some issues, not sure what I am missing:

So I used the database driver in config/session.php so I can see the data. I don't use Repositories, but refactored the services code into the respective controller methods, but I have encountered a few issues below:

  1. The user_id is null in the sessions table instead of the current auth logged in user:

screen shot 2018-11-10 at 3 53 45 pm

  1. If there is a logged in user with a laravel_token, and they are a spammer for example that must have their access revoked, I thought that removing the sessions db entry seen above that would invalidate their session/laravel_token, so on their next request they would be logged out, since they are revoked by the admin and their session no longer exists in the db. This could also happen on a password reset, if a user was logged into multiple device, lost their phone, and now resets password to invalidate all other sessions/tokens for this user.

I seem to be missing something in my understanding on the above 2 points. Any idea what is going on?

huwcarwyn commented 5 years ago

The problem here is that the laravel_token isn't used for sessions. It's used so that Laravel Passport can authenticate the request to the API. If you look in your cookies you'll see that laravel_token is still created even though you've changed session storage.

Read https://laravel.com/docs/5.7/passport#consuming-your-api-with-javascript to see exactly what I mean. You'll need to find a way to revoke this JWT that's created by Passport to invalidate "sessions"

MovingGifts commented 5 years ago

So after reading that again, it says that we need to add:

  1. CreateFreshApiToken to the web middleware, but I thought that we're using the custom EncryptCookies on the login/signup routes instead, so I think we just ignore adding the CreateFreshApiToken right?
  2. An event will be triggered for AccessTokenCreated and RefreshTokenCreated so we can prune old ones, here we can prune old tokens, but are we supposed to be saving the tokens to the database after login under the oauth_access_tokens table:
// CustomLoginController.php
public function login(Request $request) {
    // If all valid
   // This is what runs, but where is the jwt in this picture that we can revoke?
   $user = Auth::user();
   $apiCookie = $this->cookie->make($user->getKey(), $csrfToken);
   return response()->array(compact('user'))->withCookie($apiCookie); 
}

The way I used before when I used to use the Authorization Bearer header with the access token attached, which was stored in localStorage on client side. But after realizing this is not secure and it's better to use an embedded encrypted jwt token in laravel session, since it is a self consuming api:

// CustomLoginController.php
public function login(Request $request) {
    // If all valid
   $user = Auth::user();
   $token = $user->createToken('Laravel Password Grant Client')->accessToken;
   return response()->array(compact('token')); 
}

Are both of these supposed to mix together then like this?

// CustomLoginController.php
public function login(Request $request) {
    // If all valid
   // How will the `$apiCookie` get the `$token`, not sure if mixing both is correct even
   $user = Auth::user();
   $token = $user->createToken('Laravel Password Grant Client')->accessToken;
   $apiCookie = $this->cookie->make($user->getKey(), $csrfToken);
   return response()->array(compact('user'))->withCookie($apiCookie); 
}

Sorry to bring this up, but it's been many days now trying to figure this out and this is the final piece of the puzzle that I just can't figure out. Everything I came across online mostly used either the insecure Authorization Bearer with localStorage, or just the CreateFreshApiToken in web middleware which did not work for me before. This package technique is the only thing that worked by adding the encryptCookies middleware to the login/signup routes and logs in without a refresh since it's all client side. But I don't see how we can revoke the user token or why user_id is null after login in the db table above, as in points 1 and 2. Please let me know if you have any idea, I am really confused on how to get it working properly and feel like I am missing a key point somewhere. I just don't know what it is.

huwcarwyn commented 5 years ago

Correct, my project doesn't need the CreateFreshApiToken middleware.

We are not using passport oAuth functionality yet either (I plan to eventually implement this so that third parties can access the API)

You can try what's accepted in the answer here, maybe this will work?

MovingGifts commented 5 years ago

Hmmm... I just have a disconnect of how/where the jwt token is in this line: $apiCookie = $this->cookie->make($user->getKey(), $csrfToken);

Is there even a jwt being used here at all?

My use case setup is same as you, Laravel backend for api and React front end (SPA) for views. They are both in the same project directory, same as yours. Forgetting jwt for a second, does your boiler plate just create a session cookie laravel_token for the user for the front end usage after login. If so, isn't there anyway to kick that user off the system if they're a spammer for example, by somehow cancelling their access on the backend when they provide the larvel_token on subsequent requests?

huwcarwyn commented 5 years ago

Yes that's the line that creates the cookie, but if you look at what $this->cookie actually is, it's an implementation of Laravel Passport's ApiTokenCookieFactory, that creates a special cookie for authenticating as a user, you can check the source for this in Laravel Passport's repository to see how it works.

You can definitely think of it as a session, but it is entirely separate from Larave's default session stuff.

Did you try to do $user->revoke(); to see if that invalidated their laravel_token cookie?

MovingGifts commented 5 years ago

I tried it and a few other things, but it didn't seem to work. Based on this comment by a core laravel developer, when talking about the CreateFreshApiToken middleware:

JWT tokens created by this middleware aren't stored anywhere. They can't be revoked or "not exist". They simply provide a way for your api calls to be authed through the laravel_token cookie. It isn't related to access tokens.

Also: you normally wouldn't use tokens issued by clients on the same app which issues them. You'd use them in a first or third party app. Either use the middleware or the client issued tokens but not both at the same time.

Which is a bit confusing. So if there is just one laravel/react app, one api to serve it (we won't serve 3rd party apps, just this one app and its users that register, login, or just browse). Then what should one do if they want to use the security of the laravel_token, but also the flexibility of catering to these situations:

huwcarwyn commented 5 years ago

Hmmm I guess the laravel_token is closer to sessions than I thought, I think the only solution to this is to implement the password grant oAuth flow with laravel passport, that would take quite a bit of work I think, but if this was done, it would create an entry in the passport oauth_access_tokens table in the database, and these could be revoked.

MovingGifts commented 5 years ago

Yeah, it's a bit confusing to be honest, but security comes first. To me the laravel_token cookie's encrypted JWT is much more secure than using JWT any other way, especially the Authorization Bearer and localStorage combination many blogs/tutorials/videos show. This is not secure at all, but is the quickest way shared on the internet to implement unfortunately. I was excited when I came across your boiler plate because I thought it would be the missing piece, everything react side with no hard page refreshes, so much better UX, but seems like I have no idea how to solve the above scenarios after being authenticated by the cookie :/

huwcarwyn commented 5 years ago

If you implemented the password redirect oAuth flow, you could pass the access token created as a HTTP only encrypted cookie, that would provide security and allow you to revoke. It'd behave very much like the JWT, but instead it'd just be an encrypted token cookie passed along with every request.

I'm gonna close this issue now as I feel like we've arrived at a "resolution", best of luck to you on this one.