Skip to content

Commit

Permalink
Dispatch event when a JWT is authenticated
Browse files Browse the repository at this point in the history
JWTProvider::$dispatcher

Added eventDispatcher to instanciation of JWTProvider
  • Loading branch information
gamringer committed Jun 8, 2015
1 parent 53ad877 commit 7e8f799
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Encoder/JWTEncoderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function encode(array $data);
/**
* @param string $token
*
* @return array
* @return bool|array
*/
public function decode($token);
}
65 changes: 65 additions & 0 deletions Event/JWTAuthenticatedEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php


namespace Lexik\Bundle\JWTAuthenticationBundle\Event;

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* JWTCreatedEvent
*/
class JWTAuthenticatedEvent extends Event
{
/**
* @var array
*/
protected $payload;

/**
* @var TokenInterface
*/
protected $token;

/**
* @var Request
*/
protected $request;

/**
* @param array $payload
* @param TokenInterface $token
*/
public function __construct(array $payload, TokenInterface $token)
{
$this->payload = $payload;
$this->token = $token;
}

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

/**
* @param array $payload
*/
public function setPayload(array $payload)
{
$this->payload = $payload;
}

/**
* Get token
*
* @return TokenInterface
*/
public function getToken()
{
return $this->token;
}
}
6 changes: 6 additions & 0 deletions Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,10 @@ final class Events
* Hook into this event to perform additional validation on the received payload.
*/
const JWT_DECODED = 'lexik_jwt_authentication.on_jwt_decoded';

/**
* Dispatched after the token payload has been authenticated by the provider.
* Hook into this event to perform additional modification to the authenticated token using the payload.
*/
const JWT_AUTHENTICATED = 'lexik_jwt_authentication.on_jwt_authenticated';
}
1 change: 1 addition & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<call method="setUserIdentityField">
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
</call>
<argument type="service" id="event_dispatcher"/>
</service>
<!-- JWT Security Authentication Listener -->
<service id="lexik_jwt_authentication.security.authentication.listener" class="%lexik_jwt_authentication.security.authentication.listener.class%" public="false">
Expand Down
38 changes: 36 additions & 2 deletions Resources/doc/2-data-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class JWTCreatedListener

#### Events::JWT_DECODED - validate data in the JWT payload

You can access the jwt payload once it has been decoded to perform you own additional validation.
You can access the jwt payload once it has been decoded to perform you own additional validation.

``` yaml
# services.yml
Expand Down Expand Up @@ -106,6 +106,40 @@ class JWTDecodedListener
}
```

#### Events::JWT_AUTHENTICATED - customize your authenticated token

You can add attributes to the token once it has been authenticated to allow JWT properties to be used by your application.

``` yaml
# services.yml
services:
acme_api.event.jwt_authenticated_listener:
class: Acme\Bundle\ApiBundle\EventListener\JWTAuthenticatedListener
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_authenticated, method: onJWTAuthenticated }
```
Example 4 : Keep a UUID that was set into the JWT in the authenticated token
``` php
// Acme\Bundle\ApiBundle\EventListener\JWTAuthenticatedListener.php
class JWTAuthenticatedListener
{
/**
* @param JWTAuthenticatedEvent $event
*
* @return void
*/
public function onJWTAuthenticated(JWTAuthenticatedEvent $event)
{
$token = $event->getToken();
$payload = $event->getPayload();

$token->setAttribute('uuid', $payload['uuid']);
}
}
```

#### Events::AUTHENTICATION_SUCCESS - add public data to the JWT response

By default, the authentication response is just a json containing the JWT but you can add your own public data to it.
Expand All @@ -119,7 +153,7 @@ services:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }
```
Example 4 : add user roles to the response
Example 5 : add user roles to the response
``` php
// Acme\Bundle\ApiBundle\EventListener\AuthenticationSuccessListener.php
Expand Down
14 changes: 13 additions & 1 deletion Security/Authentication/Provider/JWTProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
* JWTProvider
Expand All @@ -26,6 +29,11 @@ class JWTProvider implements AuthenticationProviderInterface
*/
protected $jwtManager;

/**
* @var EventDispatcherInterface
*/
protected $dispatcher;

/**
* @var string
*/
Expand All @@ -35,10 +43,11 @@ class JWTProvider implements AuthenticationProviderInterface
* @param UserProviderInterface $userProvider
* @param JWTManagerInterface $jwtManager
*/
public function __construct(UserProviderInterface $userProvider, JWTManagerInterface $jwtManager)
public function __construct(UserProviderInterface $userProvider, JWTManagerInterface $jwtManager, EventDispatcherInterface $dispatcher)
{
$this->userProvider = $userProvider;
$this->jwtManager = $jwtManager;
$this->dispatcher = $dispatcher;
$this->userIdentityField = 'username';
}

Expand All @@ -57,6 +66,9 @@ public function authenticate(TokenInterface $token)
$authToken->setUser($user);
$authToken->setRawToken($token->getCredentials());

$event = new JWTAuthenticatedEvent($payload, $authToken);
$this->dispatcher->dispatch(Events::JWT_AUTHENTICATED, $event);

return $authToken;
}

Expand Down
4 changes: 2 additions & 2 deletions Services/JWTManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* @author Nicolas Cabot <n.cabot@lexik.fr>
*/
interface JWTManagerInterface
interface JWTManagerInterface
{
/**
* @param UserInterface $user
Expand All @@ -22,7 +22,7 @@ public function create(UserInterface $user);
/**
* @param TokenInterface $token
*
* @return bool|string
* @return bool|array
*/
public function decode(TokenInterface $token);
}
28 changes: 22 additions & 6 deletions Tests/Security/Authentication/Provider/JWTProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class JWTProviderTest extends \PHPUnit_Framework_TestCase
*/
public function testSupports()
{
$provider = new JWTProvider($this->getUserProviderMock(), $this->getJWTManagerMock());
$provider = new JWTProvider($this->getUserProviderMock(), $this->getJWTManagerMock(), $this->getEventDispatcherMock());

/** @var TokenInterface $usernamePasswordToken */
$usernamePasswordToken = $this
Expand Down Expand Up @@ -51,11 +51,12 @@ public function testAuthenticateWithInvalidJWT()
->getMock();

$userProvider = $this->getUserProviderMock();
$eventDispatcher = $this->getEventDispatcherMock();

$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(false));

$provider = new JWTProvider($userProvider, $jwtManager);
$provider = new JWTProvider($userProvider, $jwtManager, $eventDispatcher);
$provider->authenticate($jwtUserToken);
}

Expand All @@ -73,11 +74,12 @@ public function testAuthenticateWithoutUsername()
->getMock();

$userProvider = $this->getUserProviderMock();
$eventDispatcher = $this->getEventDispatcherMock();

$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(array('foo' => 'bar')));

$provider = new JWTProvider($userProvider, $jwtManager);
$provider = new JWTProvider($userProvider, $jwtManager, $eventDispatcher);
$provider->authenticate($jwtUserToken);
}

Expand All @@ -97,10 +99,12 @@ public function testAuthenticateWithNotExistingUser()
$userProvider = $this->getUserProviderMock();
$userProvider->expects($this->any())->method('loadUserByUsername')->willThrowException(new UsernameNotFoundException());

$eventDispatcher = $this->getEventDispatcherMock();

$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(array('username' => 'user')));

$provider = new JWTProvider($userProvider, $jwtManager);
$provider = new JWTProvider($userProvider, $jwtManager, $eventDispatcher);
$provider->authenticate($jwtUserToken);
}

Expand All @@ -124,10 +128,12 @@ public function testAuthenticate()
$userProvider = $this->getUserProviderMock();
$userProvider->expects($this->any())->method('loadUserByUsername')->will($this->returnValue($user));

$eventDispatcher = $this->getEventDispatcherMock();

$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(array('username' => 'user')));

$provider = new JWTProvider($userProvider, $jwtManager);
$provider = new JWTProvider($userProvider, $jwtManager, $eventDispatcher);

$this->assertInstanceOf(
'Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken',
Expand All @@ -139,7 +145,7 @@ public function testAuthenticate()
$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(array('uid' => 'user')));

$provider = new JWTProvider($userProvider, $jwtManager);
$provider = new JWTProvider($userProvider, $jwtManager, $eventDispatcher);
$provider->setUserIdentityField('uid');

$this->assertInstanceOf(
Expand Down Expand Up @@ -177,4 +183,14 @@ protected function getUserProviderMock()
->disableOriginalConstructor()
->getMock();
}

/**
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getEventDispatcherMock()
{
return $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher')
->disableOriginalConstructor()
->getMock();
}
}

0 comments on commit 7e8f799

Please sign in to comment.