Skip to content

Commit

Permalink
Define encode/decode exceptions message from Encoder, Remove auto-ren…
Browse files Browse the repository at this point in the history
…ew behavior
  • Loading branch information
chalasr committed Sep 2, 2016
1 parent 9f42741 commit cf2861d
Show file tree
Hide file tree
Showing 10 changed files with 28 additions and 156 deletions.
10 changes: 5 additions & 5 deletions Encoder/DefaultEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ public function encode(array $payload)
try {
$jws = $this->jwsProvider->create($payload);
} catch (InvalidArgumentException $e) {
throw new JWTEncodeFailureException(JWTEncodeFailureException::UNKNOWN, $e);
throw new JWTEncodeFailureException(JWTEncodeFailureException::INVALID_CONFIG, 'An error occured while trying to encode the JWT token. Please verify your configuration (private key/passphrase)', $e);
}

if (!$jws->isSigned()) {
throw new JWTEncodeFailureException(JWTEncodeFailureException::UNSIGNED_JWS);
throw new JWTEncodeFailureException(JWTEncodeFailureException::UNSIGNED_TOKEN, 'Unable to create a signed JWT from the given configuration.');
}

return $jws->getToken();
Expand All @@ -53,17 +53,17 @@ public function decode($token)
try {
$jws = $this->jwsProvider->load($token);
} catch (InvalidArgumentException $e) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_JWT, $e);
throw new JWTDecodeFailureException(JWTDecodeFailureException::INVALID_TOKEN, 'Invalid JWT Token', $e);
}

$payload = $jws->getPayload();

if ($jws->isExpired()) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_JWT, null, $payload);
throw new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_TOKEN, 'Expired JWT Token');
}

if (!$jws->isVerified()) {
throw new JWTDecodeFailureException(JWTDecodeFailureException::UNVERIFIED_JWS);
throw new JWTDecodeFailureException(JWTDecodeFailureException::UNVERIFIED_TOKEN, 'Unable to verify the given JWT through the given configuration. If the "lexik_jwt_authentication.encoder" encryption options have been changed since your last authentication, please renew the token. If the problem persists, verify that the configured keys/passphrase are valid.');
}

return $payload;
Expand Down
16 changes: 0 additions & 16 deletions Event/JWTExpiredEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,11 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\Event;

use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
use Symfony\Component\HttpFoundation\Response;

/**
* JWTExpiredEvent.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class JWTExpiredEvent extends AuthenticationFailureEvent implements JWTFailureEventInterface
{
/**
* @param ExpiredTokenException $exception
* @param Response $response
*/
public function __construct(ExpiredTokenException $exception, Response $response)
{
parent::__construct($exception, $response);
}

public function getInvalidPayload()
{
return $this->exception->getPayload();
}
}
2 changes: 1 addition & 1 deletion Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ final class Events
/**
* Dispatched when the token is expired.
* The expired token's payload can be retrieved by hooking into this event, so you can set a different
* response, containing a new token for instance.
* response.
*/
const JWT_EXPIRED = 'lexik_jwt_authentication.on_jwt_expired';
}
25 changes: 1 addition & 24 deletions Exception/ExpiredTokenException.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,12 @@

/**
* Exception that should be thrown from a {@link JWTTokenAuthenticator} implementation during
* an authentication process..
* an authentication process.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ExpiredTokenException extends AuthenticationException
{
/**
* @var array
*/
private $payload;

/**
* @param array $payload The invalidated payload
*/
public function __construct(array $payload)
{
parent::__construct();

$this->payload = $payload;
}

/**
* @return array
*/
public function getPayload()
{
return $this->payload;
}

/**
* {@inheritdoc}
*/
Expand Down
42 changes: 3 additions & 39 deletions Exception/JWTDecodeFailureException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,7 @@
*/
class JWTDecodeFailureException extends JWTFailureException
{
const INVALID_JWT = 'invalid_jwt';
const UNVERIFIED_JWS = 'unverified_jws';
const EXPIRED_JWT = 'expired_jwt';

/**
* @var array|null
*/
private $invalidPayload;

/**
* @param string $reason
* @param \Exception $previous
* @param array|null $invalidPayload The invalid payload, should be set
* only if the reason is EXPIRED_JWT
*/
public function __construct($reason = self::INVALID_JWT, \Exception $previous = null, array $invalidPayload = null)
{
$message = 'Invalid JWT Token';

if (self::EXPIRED_JWT === $reason) {
$message = 'Expired JWT Token';
} elseif (self::UNVERIFIED_JWS === $reason) {
$message = 'Unable to verify the given JWT through the given configuration. If the "lexik_jwt_authentication.encoder" encryption options have been changed since your last authentication, please renew the token. If the problem persists, verify that the configured keys/passphrase are valid.';
}

$this->invalidPayload = $invalidPayload;

parent::__construct($reason, $message, $previous);
}

/**
* Gets the invalid payload.
*
* @return array|null
*/
public function getInvalidPayload()
{
return $this->invalidPayload;
}
const INVALID_TOKEN = 'invalid_token';
const UNVERIFIED_TOKEN = 'unverified_token';
const EXPIRED_TOKEN = 'expired_token';
}
15 changes: 2 additions & 13 deletions Exception/JWTEncodeFailureException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@
*/
class JWTEncodeFailureException extends JWTFailureException
{
const INVALID_KEY = 'invalid_key';
const UNSIGNED_JWS = 'unsigned_jws';

public function __construct($reason = self::INVALID_JWT, \Exception $previous = null)
{
$message = 'An error occured while trying to encode the JWT token.';

if (self::UNSIGNED_JWS === $reason) {
$message = 'Unable to create a signed JWT from the given configuration.';
}

parent::__construct($reason, $message, $previous);
}
const INVALID_CONFIG = 'invalid_config';
const UNSIGNED_TOKEN = 'unsigned_token';
}
40 changes: 12 additions & 28 deletions Resources/doc/2-data-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,54 +324,38 @@ public function onJWTNotFound(JWTNotFoundEvent $event)
}
```

#### Events::JWT_EXPIRED - customize the response on expired token
#### Events::JWT_EXPIRED - customize the response message on expired token

By default, if the token provided in the request is expired, the authentication listener will call the entry point returning an unauthorized (401) json response.
Thanks to this event, you can set a custom response.
Thanks to this event, you can set a custom response or simply change the response message.

``` yaml
# services.yml
services:
acme_api.event.jwt_invalid_listener:
acme_api.event.jwt_expired_listener:
class: Acme\Bundle\ApiBundle\EventListener\JWTExpiredListener
arguments:
- '@lexik_jwt_authentication.jwt_manager'
- '@app.security_user_loader'
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_expired, method: renewToken }
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_expired, method: onJWTExpired }
```
Example 8: attach a new token to the response in case of expired token
Example 8: customize the response in case of expired token
``` php
// Acme\Bundle\ApiBundle\EventListener\JWTExpiredListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;

/**
* @param JWTTokenManagerInterface $jwtManager
* @param UserLoaderInterface $userLoader
*/
public function __construct(JWTTokenManagerInterface $jwtManager, UserLoaderInterface $userLoader)
{
$this->jwtManager = $jwtManager;
$this->userLoader = $userLoader;
}

/**
* @param JWTExpiredEvent $event
*/
public function renewToken(JWTExpiredEvent $event)
public function onJWTExpired(JWTExpiredEvent $event)
{
$payload = $event->getPayload();
$user = $this->userLoader->loadUserByUsername($payload)
$token = $this->jwtManager->create($user);

$event->setResponse(new JWTAuthenticationSuccessResponse($token));
/** @var \Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse */
$response = $event->getResponse();

$response->setMessage('Your token is expired, please renew it.');
}
```

__Protip:__ You might want to use the same method for customizing the response on both `JWT_INVALID` and `JWT_NOT_FOUND` events.
For that, use the `Event\JWTFailureEventInterface` interface to typehint the event argument of your listener's method, rather than
a specific event class (i.e. `JWTNotFoundEvent` or `JWTInvalidEvent`).
__Protip:__ You might want to use the same method for customizing the response on both `JWT_INVALID`, `JWT_NOT_FOUND` and/or `JWT_EXPIRED` events.
For that, use the `Event\JWTFailureEventInterface` interface to type-hint the event argument of your listener's method, rather the class corresponding to one of these specific events.
7 changes: 3 additions & 4 deletions Security/Guard/JWTTokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public function getCredentials(Request $request)

$preAuthToken->setPayload($payload);
} catch (JWTDecodeFailureException $e) {
if (JWTDecodeFailureException::EXPIRED_JWT === $e->getReason()) {
throw new ExpiredTokenException($e->getInvalidPayload());
if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
throw new ExpiredTokenException();
}

throw JWTAuthenticationException::invalidToken($e);
Expand Down Expand Up @@ -151,8 +151,7 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
$authException,
// After adding other AuthException classes, assign $response
// before the check and use it for both events, using getMessageKey()
new JWTAuthenticationFailureResponse($authException->getMessageKey()),
$authException->getPayload()
new JWTAuthenticationFailureResponse($authException->getMessageKey())
);
$this->dispatcher->dispatch(Events::JWT_EXPIRED, $event);
} else {
Expand Down
25 changes: 0 additions & 25 deletions Tests/Functional/SubscribedTokenAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationSuccessResponse;
use Lexik\Bundle\JWTAuthenticationBundle\Tests\Stubs\User;

/**
* Tests the overriding authentication response mechanism.
Expand Down Expand Up @@ -94,27 +92,4 @@ public function testAccessSecuredRouteWithExpiredToken($fail = true)

self::$subscriber->unsetListener(Events::JWT_EXPIRED);
}

/**
* @group time-sensitive
*/
public function testRenewTokenOnJWTExpired()
{
static::bootKernel();
$jwtManager = static::$kernel->getContainer()->get('lexik_jwt_authentication.jwt_manager');

self::$subscriber->setListener(Events::JWT_EXPIRED, function (JWTExpiredEvent $e) use ($jwtManager) {
$invalidPayload = $e->getInvalidPayload();
$user = new User($invalidPayload['username'], '');
$e->setResponse(new JWTAuthenticationSuccessResponse($jwtManager->create($user)));
});

$response = parent::testAccessSecuredRouteWithExpiredToken(false);

$this->assertArrayHasKey('token', $response);

parent::testAccessSecuredRoute($response['token']);

self::$subscriber->unsetListener(Events::JWT_EXPIRED);
}
}
2 changes: 1 addition & 1 deletion Tests/Security/Guard/JWTTokenAuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function testGetCredentialsWithExpiredToken()
->expects($this->once())
->method('decode')
->with(new PreAuthenticationJWTUserToken('token'))
->will($this->throwException(new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_JWT, null, [])));
->will($this->throwException(new JWTDecodeFailureException(JWTDecodeFailureException::EXPIRED_TOKEN, 'Expired JWT Token')));

try {
(new JWTTokenAuthenticator(
Expand Down

0 comments on commit cf2861d

Please sign in to comment.