Skip to content

Commit

Permalink
Added Single Use config option (#148)
Browse files Browse the repository at this point in the history
Generate a new refresh token on consumption
  • Loading branch information
fastnloud authored and markitosgv committed Jul 1, 2019
1 parent 327dfbd commit 60592e7
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 3 deletions.
4 changes: 4 additions & 0 deletions DependencyInjection/Configuration.php
Expand Up @@ -57,6 +57,10 @@ public function getConfigTreeBuilder()
->defaultNull()
->info('Deprecated, use object_manager instead')
->end()
->scalarNode('single_use')
->defaultFalse()
->info('When true, generate a new refresh token on consumption (deleting the old one)')
->end()
->scalarNode('token_parameter_name')->defaultValue('refresh_token')->end()
->end();

Expand Down
1 change: 1 addition & 0 deletions DependencyInjection/GesdinetJWTRefreshTokenExtension.php
Expand Up @@ -39,6 +39,7 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('gesdinet_jwt_refresh_token.security.firewall', $config['firewall']);
$container->setParameter('gesdinet_jwt_refresh_token.user_provider', $config['user_provider']);
$container->setParameter('gesdinet_jwt_refresh_token.user_identity_field', $config['user_identity_field']);
$container->setParameter('gesdinet_jwt_refresh_token.single_use', $config['single_use']);
$container->setParameter('gesdinet_jwt_refresh_token.token_parameter_name', $config['token_parameter_name']);

$refreshTokenClass = 'Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken';
Expand Down
20 changes: 19 additions & 1 deletion EventListener/AttachRefreshTokenOnSuccessListener.php
Expand Up @@ -11,6 +11,7 @@

namespace Gesdinet\JWTRefreshTokenBundle\EventListener;

use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
use Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshToken;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
Expand Down Expand Up @@ -51,6 +52,11 @@ class AttachRefreshTokenOnSuccessListener
*/
protected $tokenParameterName;

/**
* @var bool
*/
protected $singleUse;

/**
* AttachRefreshTokenOnSuccessListener constructor.
*
Expand All @@ -60,21 +66,24 @@ class AttachRefreshTokenOnSuccessListener
* @param RequestStack $requestStack
* @param string $userIdentityField
* @param string $tokenParameterName
* @param bool $singleUse
*/
public function __construct(
RefreshTokenManagerInterface $refreshTokenManager,
$ttl,
ValidatorInterface $validator,
RequestStack $requestStack,
$userIdentityField,
$tokenParameterName
$tokenParameterName,
$singleUse
) {
$this->refreshTokenManager = $refreshTokenManager;
$this->ttl = $ttl;
$this->validator = $validator;
$this->requestStack = $requestStack;
$this->userIdentityField = $userIdentityField;
$this->tokenParameterName = $tokenParameterName;
$this->singleUse = $singleUse;
}

public function attachRefreshToken(AuthenticationSuccessEvent $event)
Expand All @@ -89,6 +98,15 @@ public function attachRefreshToken(AuthenticationSuccessEvent $event)

$refreshTokenString = RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);

if ($refreshTokenString && true === $this->singleUse) {
$refreshToken = $this->refreshTokenManager->get($refreshTokenString);
$refreshTokenString = null;

if ($refreshToken instanceof RefreshTokenInterface) {
$this->refreshTokenManager->delete($refreshToken);
}
}

if ($refreshTokenString) {
$data[$this->tokenParameterName] = $refreshTokenString;
} else {
Expand Down
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -231,6 +231,17 @@ gesdinet_jwt_refresh_token:

You will probably want to use a custom UserProvider along with your UserChecker to ensure that the checker recieves the right type of user.

### Config Single Use

You can configure the refresh token so it can only be consumed _once_. If set to `true` and the refresh token is consumed, a new refresh token will be provided.

To enable this behavior add this line to your config:

```yaml
gesdinet_jwt_refresh_token:
single_use: true
```


### Use another entity for refresh tokens

Expand Down Expand Up @@ -337,6 +348,8 @@ This refresh token is persisted in RefreshToken entity. After that, when your JW

- Ask to renew valid JWT with our refresh token. Make a POST call to /api/token/refresh url with refresh token as payload. In this way, you can always get a valid JWT without asking for user credentials. But **you must notice** if refresh token is still valid. Your refresh token do not change but valid datetime will increase.

***Note that when a refresh token is consumed and the config option `single_use` is set to `true` the token will no longer be valid.***

```bash
curl -X POST -d refresh_token="xxxx4b54b0076d2fcc5a51a6e60c0fb83b0bc90b47e2c886accb70850795fb311973c9d101fa0111f12eec739db063ec09d7dd79331e3148f5fc6e9cb362xxxx" 'http://xxxx/token/refresh'
```
Expand Down
2 changes: 1 addition & 1 deletion Resources/config/services.yml
@@ -1,7 +1,7 @@
services:
gesdinet.jwtrefreshtoken.send_token:
class: Gesdinet\JWTRefreshTokenBundle\EventListener\AttachRefreshTokenOnSuccessListener
arguments: [ "@gesdinet.jwtrefreshtoken.refresh_token_manager", "%gesdinet_jwt_refresh_token.ttl%", "@validator", "@request_stack", "%gesdinet_jwt_refresh_token.user_identity_field%", "%gesdinet_jwt_refresh_token.token_parameter_name%" ]
arguments: [ "@gesdinet.jwtrefreshtoken.refresh_token_manager", "%gesdinet_jwt_refresh_token.ttl%", "@validator", "@request_stack", "%gesdinet_jwt_refresh_token.user_identity_field%", "%gesdinet_jwt_refresh_token.token_parameter_name%", "%gesdinet_jwt_refresh_token.single_use%" ]
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: attachRefreshToken }

Expand Down
Expand Up @@ -33,6 +33,7 @@ public function it_should_set_parameters_correctly(ContainerBuilder $container,
return new \ReflectionClass($args[0]);
});
$container->addResource(Argument::any())->willReturn(null);
$container->addRemovedBindingIds(Argument::any())->willReturn(null);

$container->removeBindings(Argument::any())->will(function () {
});
Expand Down
Expand Up @@ -23,7 +23,8 @@ public function let(RefreshTokenManagerInterface $refreshTokenManager, Validator
{
$ttl = 2592000;
$userIdentityField = 'username';
$this->beConstructedWith($refreshTokenManager, $ttl, $validator, $requestStack, $userIdentityField, self::TOKEN_PARAMETER_NAME);
$singleUse = false;
$this->beConstructedWith($refreshTokenManager, $ttl, $validator, $requestStack, $userIdentityField, self::TOKEN_PARAMETER_NAME, $singleUse);
}

public function it_is_initializable()
Expand Down

0 comments on commit 60592e7

Please sign in to comment.