-
-
Notifications
You must be signed in to change notification settings - Fork 611
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 2.x: Update CHANGELOG for v2.21.0 Invalidate a JWT token - Adding the jti claim by the JWTManager class instead of doing it via a listener feat: Invalidate a JWT token
- Loading branch information
Showing
36 changed files
with
943 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
DependencyInjection/Compiler/CollectPayloadEnrichmentsPass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler; | ||
|
||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
class CollectPayloadEnrichmentsPass implements CompilerPassInterface | ||
{ | ||
use PriorityTaggedServiceTrait; | ||
|
||
public function process(ContainerBuilder $container): void | ||
{ | ||
if (!$container->hasDefinition('lexik_jwt_authentication.payload_enrichment')) { | ||
return; | ||
} | ||
|
||
$container->getDefinition('lexik_jwt_authentication.payload_enrichment') | ||
->replaceArgument(0, $this->findAndSortTaggedServices('lexik_jwt_authentication.payload_enrichment', $container)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\EventListener; | ||
|
||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingClaimException; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Services\BlockedTokenManagerInterface; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Services\CacheItemPoolBlockedTokenManager; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | ||
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\Security\Core\Exception\DisabledException; | ||
use Symfony\Component\Security\Http\Event\LoginFailureEvent; | ||
use Symfony\Component\Security\Http\Event\LogoutEvent; | ||
|
||
class BlockJWTListener | ||
{ | ||
private $blockedTokenManager; | ||
private $tokenExtractor; | ||
private $jwtManager; | ||
|
||
public function __construct( | ||
BlockedTokenManagerInterface $blockedTokenManager, | ||
TokenExtractorInterface $tokenExtractor, | ||
JWTTokenManagerInterface $jwtManager | ||
) { | ||
$this->blockedTokenManager = $blockedTokenManager; | ||
$this->tokenExtractor = $tokenExtractor; | ||
$this->jwtManager = $jwtManager; | ||
} | ||
|
||
public function onLoginFailure(LoginFailureEvent $event): void | ||
{ | ||
$exception = $event->getException(); | ||
if (($exception instanceof DisabledException) || ($exception->getPrevious() instanceof DisabledException)) { | ||
$this->blockTokenFromRequest($event->getRequest()); | ||
} | ||
} | ||
|
||
public function onLogout(LogoutEvent $event): void | ||
{ | ||
$this->blockTokenFromRequest($event->getRequest()); | ||
} | ||
|
||
private function blockTokenFromRequest(Request $request): void | ||
{ | ||
$token = $this->tokenExtractor->extract($request); | ||
|
||
if ($token === false) { | ||
// There's nothing to block if the token isn't in the request | ||
return; | ||
} | ||
|
||
try { | ||
$payload = $this->jwtManager->parse($token); | ||
} catch (JWTDecodeFailureException $e) { | ||
// Ignore decode failures, this would mean the token is invalid anyway | ||
return; | ||
} | ||
|
||
try { | ||
$this->blockedTokenManager->add($payload); | ||
} catch (MissingClaimException $e) { | ||
// We can't block a token missing the claims our system requires, so silently ignore this one | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\EventListener; | ||
|
||
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingClaimException; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Services\BlockedTokenManagerInterface; | ||
|
||
class RejectBlockedTokenListener | ||
{ | ||
private $blockedTokenManager; | ||
|
||
public function __construct(BlockedTokenManagerInterface $blockedTokenManager) | ||
{ | ||
$this->blockedTokenManager = $blockedTokenManager; | ||
} | ||
|
||
/** | ||
* @throws InvalidTokenException if the JWT is blocked | ||
*/ | ||
public function __invoke(JWTAuthenticatedEvent $event): void | ||
{ | ||
try { | ||
if ($this->blockedTokenManager->has($event->getPayload())) { | ||
throw new InvalidTokenException('JWT blocked'); | ||
} | ||
} catch (MissingClaimException $e) { | ||
// Do nothing if the required claims do not exist on the payload (older JWTs won't have the "jti" claim the manager requires) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\Exception; | ||
|
||
use Throwable; | ||
|
||
class MissingClaimException extends JWTFailureException | ||
{ | ||
public function __construct( | ||
string $claim, | ||
Throwable $previous = null | ||
) { | ||
parent::__construct('missing_claim', sprintf('Missing required "%s" claim on JWT payload.', $claim), $previous); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?xml version="1.0" ?> | ||
|
||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="lexik_jwt_authentication.event_listener.block_jwt_listener" class="Lexik\Bundle\JWTAuthenticationBundle\EventListener\BlockJWTListener"> | ||
<argument type="service" id="lexik_jwt_authentication.blocked_token_manager"/> | ||
<argument type="service" id="lexik_jwt_authentication.extractor.chain_extractor"/> | ||
<argument type="service" id="lexik_jwt_authentication.jwt_manager"/> | ||
<tag name="kernel.event_listener" event="Symfony\Component\Security\Http\Event\LoginFailureEvent" method="onLoginFailure" dispatcher="event_dispatcher"/> | ||
<tag name="kernel.event_listener" event="Symfony\Component\Security\Http\Event\LogoutEvent" method="onLogout" dispatcher="event_dispatcher"/> | ||
</service> | ||
|
||
<service id="lexik_jwt_authentication.event_listener.reject_blocked_token_listener" class="Lexik\Bundle\JWTAuthenticationBundle\EventListener\RejectBlockedTokenListener"> | ||
<argument type="service" id="lexik_jwt_authentication.blocked_token_manager"/> | ||
<tag name="kernel.event_listener" event="lexik_jwt_authentication.on_jwt_authenticated"/> | ||
</service> | ||
|
||
<service id="lexik_jwt_authentication.blocked_token_manager" class="Lexik\Bundle\JWTAuthenticationBundle\Services\BlockedToken\CacheItemPoolBlockedTokenManager"> | ||
<argument type="service" id="lexik_jwt_authentication.blocklist_token.cache"/> | ||
</service> | ||
|
||
<service id="Lexik\Bundle\JWTAuthenticationBundle\Services\BlockedTokenManagerInterface" alias="lexik_jwt_authentication.blocked_token_manager" /> | ||
</services> | ||
|
||
</container> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
Invalidate token | ||
================ | ||
|
||
The token blocklist relies on the ``jti`` claim, a standard claim designed for tracking and revoking JWTs. `"jti" (JWT ID) Claim <https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7>`_ | ||
|
||
The blocklist storage utilizes a cache implementing ``Psr\Cache\CacheItemPoolInterface``. The cache stores the ``jti`` of the blocked token to the cache, and the cache item expires after the "exp" (expiration time) claim of the token | ||
|
||
Configuration | ||
~~~~~~~~~~~~~ | ||
|
||
To configure token blocklist, update your `lexik_jwt_authentication.yaml` file: | ||
|
||
.. code-block:: yaml | ||
# config/packages/lexik_jwt_authentication.yaml | ||
# ... | ||
lexik_jwt_authentication: | ||
# ... | ||
# invalidate the token on logout by storing it in the cache | ||
blocklist_token: | ||
enabled: true | ||
cache: cache.app | ||
Enabling ``blocklist_token``: | ||
|
||
* Adds a ``jti`` claim to the payload via `Lexik\Bundle\JWTAuthenticationBundle\Services\PayloadEnrichment\RandomJtiEnrichment` passed as an argument to the `Lexik\Bundle\JWTAuthenticationBundle\Services\JwtManager` | ||
|
||
* activates the event listener ``Lexik\Bundle\JWTAuthenticationBundle\BlockJWTListener`` which blocks JWTs on logout (``Symfony\Component\Security\Http\Event\LogoutEvent``) | ||
or on login failure due to the user not being enabled (``Symfony\Component\Security\Core\Exception\DisabledException``) | ||
|
||
* activates an event listener ``Lexik\Bundle\JWTAuthenticationBundle\RejectBlockedTokenListener`` which rejects blocked tokens during authentication | ||
|
||
To block JWTs on logout, you must either activate logout in the firewall configuration or do it programmatically | ||
|
||
* by firewall configuration | ||
|
||
.. code-block:: yaml | ||
# config/packages/security.yaml | ||
security: | ||
enable_authenticator_manager: true | ||
firewalls: | ||
api: | ||
... | ||
jwt: ~ | ||
logout: | ||
path: app_logout | ||
* programmatically in a controller action | ||
|
||
.. code-block:: php | ||
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||
use Symfony\Component\HttpFoundation\JsonResponse; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
use Symfony\Component\Security\Http\Event\LogoutEvent; | ||
//... | ||
class SecurityController | ||
{ | ||
//... | ||
public function logout(Request $request, EventDispatcherInterface $eventDispatcher, TokenStorageInterface $tokenStorage) | ||
{ | ||
$eventDispatcher->dispatch(new LogoutEvent($request, $tokenStorage->getToken())); | ||
return new JsonResponse(); | ||
} | ||
] | ||
Refer to `Symfony logging out <https://symfony.com/doc/current/security.html#logging-out>`_ for more details. | ||
|
||
Changing blocklist storage | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
To change the blocklist storage, refer to `Configuring Cache with FrameworkBundle <https://symfony.com/doc/current/cache.html#configuring-cache-with-frameworkbundle>`_ | ||
|
||
.. code-block:: yaml | ||
# config/packages/framework.yaml | ||
framework: | ||
# ... | ||
cache: | ||
default_redis_provider: 'redis://localhost' | ||
pools: | ||
block_list_token_cache_pool: | ||
adapter: cache.adapter.redis | ||
# ... | ||
blocklist_token: | ||
enabled: true | ||
cache: block_list_token_cache_pool |
Oops, something went wrong.