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鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
UX with cookie-based refresh tokens #287
Comments
Hey,
Yeah, this is similar to what I do in my applications. I use a custom route Here's the code (adapted from something I found in the comments of another issue which I cannot find anymore): src/Controller/InvalidateRefreshTokenController.php<?php
namespace App\Controller;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
class InvalidateRefreshTokenController extends AbstractController
{
/**
* @var RefreshTokenManagerInterface
*/
private $refreshTokenManager;
/**
* @var ExtractorInterface
*/
private $refreshTokenExtractor;
public function __construct(
RefreshTokenManagerInterface $refreshTokenManager,
ExtractorInterface $refreshTokenExtractor
) {
$this->refreshTokenManager = $refreshTokenManager;
$this->refreshTokenExtractor = $refreshTokenExtractor;
}
public function invalidate(Request $request): JsonResponse
{
// TODO: get tokenParameter from service configuration
$tokenParameter = 'refresh_token';
$tokenString = $this->refreshTokenExtractor->getRefreshToken($request, $tokenParameter);
if (!$tokenString) {
return new JsonResponse(
[
'code' => 422,
'message' => 'No refresh_token found.',
],
JsonResponse::HTTP_UNPROCESSABLE_ENTITY
);
}
$refreshToken = $this->refreshTokenManager->get($tokenString);
if (!$refreshToken) {
return new JsonResponse(
[
'code' => 422,
'message' => 'Invalid refresh_token supplied.',
],
JsonResponse::HTTP_UNPROCESSABLE_ENTITY
);
}
$this->refreshTokenManager->delete($refreshToken);
$response = new JsonResponse(
[
'code' => 200,
'message' => 'The refresh_token has been invalidated.',
],
JsonResponse::HTTP_OK
);
// TODO: check if cookies are enabled in service configuration
$response->headers->clearCookie($tokenParameter);
return $response;
}
}
This is actually the case with any (traditional) cookie-based authentication. I also personally expect this kind of behavior, as I tend to use multiple tabs and expect to be the same user everywhere, or still be logged in on a new tab. (I use private browsing for a second session) |
Hi @Jayfrown - yeah, that's pretty similar to what I did, but I was wondering if this feature should not be part of this bundle, otherwise user has no way to properly "log out". Regarding my 2nd point, I came up with a crappy but very efficient solution to expose my API on 2 different domains, so that employee cookies and user cookies remain isolated. 馃憤 |
I have tried several times now to take a look at including this behavior by default, as I agree that currently it is a bit awkward that one has to ship their own controller in order to invalidate the However, I find myself too inexperienced working with Symfony's authenticator system, and cannot quite figure out where to begin. @mbabker would you mind sharing your thoughts/opinions on the matter and giving me some direction? |
My initial thought is that this bundle will need an event listener for the logout event to be able to destroy the cookie. |
Or, the security:
firewalls:
api:
logout:
delete_cookies:
<cookie_name>:
path: ~
domain: ~
secure: false
samesite: ~ |
Great, thanks! I'll experiment with the # in security.yaml
security:
firewalls:
api:
logout:
path: api_token_invalidate # in routes.yaml
api_token_invalidate:
path: /api/token/invalidate
methods: ['POST'] I believe then |
So, using the # in gesdinet_jwt_refresh_token.yaml
gesdinet_jwt_refresh_token:
ttl: 7200
single_use: true
cookie:
enabled: true
path: /api/token/
same_site: none # in security.yaml
security:
firewalls:
api:
logout:
path: api_token_invalidate
delete_cookies:
refresh_token:
path: /api/token/
samesite: none # in routes.yaml
api_token_invalidate:
path: /api/token/invalidate However, there are some things to consider:
So I think it would make more sense to include an I will start working on a PR for this soon. |
This commit introcudes a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If there is no `refresh_token`, an error is returned and the cookie is not unset. The same happens if the supplied `refresh_token` is invalid. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
This commit introcudes a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If there is no `refresh_token`, an error is returned and the cookie is not unset. The same happens if the supplied `refresh_token` is invalid. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
This commit introcudes a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If there is no `refresh_token`, an error is returned but the cookie is still unset. The same happens if the supplied `refresh_token` is invalid. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
PR: #302 |
This commit introduces a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If there is no `refresh_token`, an error is returned but the cookie is still unset. The same happens if the supplied `refresh_token` is invalid. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
This commit introduces a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If there is no `refresh_token`, an error is returned but the cookie is still unset. The same happens if the supplied `refresh_token` is invalid. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
This commit introduces a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If no refresh token is supplied, an error is returned and the cookie remains untouched. If the supplied refresh token is (already) invalid, the cookie is unset. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
This commit introduces a `LogoutEventListener` which invalidates the given `refresh_token` and unsets the cookie, if enabled. If no refresh token is supplied, an error is returned and the cookie remains untouched. If the supplied refresh token is (already) invalid, the cookie is unset. Because the `LogoutEventListener` always sets a response, it would inhibit normal logout behavior and therefore should only run on a specifically configured firewall. Therefore a new configuration option is introduced, called `logout_firewall`, which contains the name of the firewall that triggers the logout event we want to hook into (default: `api`).
Hello there! 馃憢
I've been testing the "cookie" option of this bundle, lately.
This is awesome and it works great! 馃帀 It avoids having to store the token on the client side and expose it to creepy people.
However, I notice 2 usability issues with this:
When the user logs out from the SPA, client just destroys the access token and the user gets redirected to the login page. Fine. problem is, since client has not access to the refreshToken anymore, it can no longer destroy it. This means that next call to the refresh token route will log the user in, even if they wanted to logout, because the cookie is still here 馃槗
Since the refreshToken is no longer the client's business, 2 different users can't be logged in within the same browser. I usually have an "admin" tab and a "customer" tab. Since both share the same cookie (same API), as soon as I sign in as a customer, I get kicked from the admin tab once this one asks for a new token.
Regarding 1: how about the following flow:
Regarding 2... I have no real idea on how to address this. Thoughts?
The text was updated successfully, but these errors were encountered: