Skip to content

Commit

Permalink
Customizable User ID Claim
Browse files Browse the repository at this point in the history
  • Loading branch information
Spomky authored and chalasr committed Aug 10, 2018
1 parent 85b0578 commit 0862239
Show file tree
Hide file tree
Showing 18 changed files with 111 additions and 42 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
CHANGELOG
=========

For a diff between two versions https://github.com/lexik/LexikJWTAuthenticationBundle/compare/v1.0.0...v2.5.3
For a diff between two versions https://github.com/lexik/LexikJWTAuthenticationBundle/compare/v1.0.0...v2.5.4

## [2.5.4](https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v2.5.4) (2018-08-2)

* bug [\#542](https://github.com/lexik/LexikJWTAuthenticationBundle/pull/542) Fix missing implements breaking JWT header alteration ([tucksaun](https://github.com/tucksaun))

## [2.5.3](https://github.com/lexik/LexikJWTAuthenticationBundle/tree/v2.5.3) (2018-07-6)

Expand Down
4 changes: 4 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public function getConfigTreeBuilder()
->defaultValue('username')
->cannotBeEmpty()
->end()
->scalarNode('user_id_claim')
->defaultNull()
->info('If null, the user ID clam will be have the same name as the one defined by the option "user_identity_field"')
->end()
->append($this->getTokenExtractorsNode())
->end();

Expand Down
3 changes: 3 additions & 0 deletions DependencyInjection/LexikJWTAuthenticationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('lexik_jwt_authentication.token_ttl', $config['token_ttl']);
$container->setParameter('lexik_jwt_authentication.clock_skew', $config['clock_skew']);
$container->setParameter('lexik_jwt_authentication.user_identity_field', $config['user_identity_field']);

$user_id_claim = $config['user_id_claim'] ? $config['user_id_claim'] : $config['user_identity_field'];
$container->setParameter('lexik_jwt_authentication.user_id_claim', $user_id_claim);
$encoderConfig = $config['encoder'];

if ('lexik_jwt_authentication.encoder.default' === $encoderConfig['service']) {
Expand Down
2 changes: 1 addition & 1 deletion Encoder/LcobucciJWTEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class LcobucciJWTEncoder implements JWTEncoderInterface
class LcobucciJWTEncoder implements JWTEncoderInterface, HeaderAwareJWTEncoderInterface
{
/**
* @var JWSProviderInterface
Expand Down
1 change: 1 addition & 0 deletions Resources/config/deprecated.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<service id="lexik_jwt_authentication.security.authentication.provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider\JWTProvider" public="false">
<argument /> <!-- User Provider -->
<argument type="service" id="lexik_jwt_authentication.jwt_manager" />
<argument>%lexik_jwt_authentication.user_id_claim%</argument>
<call method="setUserIdentityField">
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
</call>
Expand Down
1 change: 1 addition & 0 deletions Resources/config/jwt_manager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<service id="lexik_jwt_authentication.jwt_manager" class="Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager" public="true">
<argument type="service" id="lexik_jwt_authentication.encoder"/>
<argument type="service" id="event_dispatcher"/>
<argument>%lexik_jwt_authentication.user_id_claim%</argument>
<call method="setUserIdentityField">
<argument>%lexik_jwt_authentication.user_identity_field%</argument>
</call>
Expand Down
22 changes: 19 additions & 3 deletions Security/Authentication/Provider/JWTProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,30 @@ class JWTProvider implements AuthenticationProviderInterface
*/
protected $userIdentityField;

/**
* @var string
*/
private $userIdClaim;

/**
* @param UserProviderInterface $userProvider
* @param JWTManagerInterface $jwtManager
* @param EventDispatcherInterface $dispatcher
* @param string $userIdClaim
*/
public function __construct(
UserProviderInterface $userProvider,
JWTManagerInterface $jwtManager,
EventDispatcherInterface $dispatcher
EventDispatcherInterface $dispatcher,
$userIdClaim
) {
@trigger_error(sprintf('The "%s" class is deprecated since version 2.0 and will be removed in 3.0. See "%s" instead.', __CLASS__, JWTTokenAuthenticator::class), E_USER_DEPRECATED);

$this->userProvider = $userProvider;
$this->jwtManager = $jwtManager;
$this->dispatcher = $dispatcher;
$this->userIdentityField = 'username';
$this->userIdClaim = $userIdClaim;
}

/**
Expand Down Expand Up @@ -97,11 +105,11 @@ public function authenticate(TokenInterface $token)
*/
protected function getUserFromPayload(array $payload)
{
if (!isset($payload[$this->userIdentityField])) {
if (!isset($payload[$this->userIdClaim])) {
throw $this->createAuthenticationException();
}

return $this->userProvider->loadUserByUsername($payload[$this->userIdentityField]);
return $this->userProvider->loadUserByUsername($payload[$this->userIdClaim]);
}

/**
Expand All @@ -128,6 +136,14 @@ public function setUserIdentityField($userIdentityField)
$this->userIdentityField = $userIdentityField;
}

/**
* @return string
*/
public function getUserIdClaim()
{
return $this->userIdClaim;
}

/**
* @param JWTDecodeFailureException $previous
*
Expand Down
13 changes: 7 additions & 6 deletions Security/Guard/JWTTokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,20 @@ public function getUser($preAuthToken, UserProviderInterface $userProvider)
);
}

$payload = $preAuthToken->getPayload();
$identityField = $this->jwtManager->getUserIdentityField();
$payload = $preAuthToken->getPayload();
$idClaim = $this->jwtManager->getUserIdClaim();

if (!isset($payload[$identityField])) {
throw new InvalidPayloadException($identityField);

if (!isset($payload[$idClaim])) {
throw new InvalidPayloadException($idClaim);
}

$identity = $payload[$identityField];
$identity = $payload[$idClaim];

try {
$user = $this->loadUser($userProvider, $payload, $identity);
} catch (UsernameNotFoundException $e) {
throw new UserNotFoundException($identityField, $identity);
throw new UserNotFoundException($idClaim, $identity);
}

$this->preAuthenticationTokenStorage->setToken($preAuthToken);
Expand Down
21 changes: 18 additions & 3 deletions Services/JWTManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,22 @@ class JWTManager implements JWTManagerInterface, JWTTokenManagerInterface
*/
protected $userIdentityField;

/**
* @var string
*/
protected $userIdClaim;

/**
* @param JWTEncoderInterface $encoder
* @param EventDispatcherInterface $dispatcher
* @param string $userIdClaim
*/
public function __construct(JWTEncoderInterface $encoder, EventDispatcherInterface $dispatcher)
public function __construct(JWTEncoderInterface $encoder, EventDispatcherInterface $dispatcher, $userIdClaim)
{
$this->jwtEncoder = $encoder;
$this->dispatcher = $dispatcher;
$this->userIdentityField = 'username';
$this->userIdClaim = $userIdClaim;
}

/**
Expand Down Expand Up @@ -99,8 +106,8 @@ public function decode(TokenInterface $token)
*/
protected function addUserIdentityToPayload(UserInterface $user, array &$payload)
{
$accessor = PropertyAccess::createPropertyAccessor();
$payload[$this->userIdentityField] = $accessor->getValue($user, $this->userIdentityField);
$accessor = PropertyAccess::createPropertyAccessor();
$payload[$this->userIdClaim] = $accessor->getValue($user, $this->userIdentityField);
}

/**
Expand All @@ -118,4 +125,12 @@ public function setUserIdentityField($userIdentityField)
{
$this->userIdentityField = $userIdentityField;
}

/**
* @return string
*/
public function getUserIdClaim()
{
return $this->userIdClaim;
}
}
7 changes: 7 additions & 0 deletions Services/JWTTokenManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ public function setUserIdentityField($field);
* @return string
*/
public function getUserIdentityField();

/**
* Returns the claim used as identifier to load an user from a JWT payload.
*
* @return string
*/
public function getUserIdClaim();
}
3 changes: 2 additions & 1 deletion Tests/Functional/CompleteTokenAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public function testExpClaimIsNotSetIfNoTTL()
{
static::bootKernel();
$encoder = static::$kernel->getContainer()->get('lexik_jwt_authentication.encoder');
$idClaim = static::$kernel->getContainer()->getParameter('lexik_jwt_authentication.user_id_claim');

$r = new \ReflectionProperty(get_class($encoder), 'jwsProvider');
$r->setAccessible(true);
Expand All @@ -96,7 +97,7 @@ public function testExpClaimIsNotSetIfNoTTL()
$this->ttl = null;
}, $jwsProvider, get_class($jwsProvider))->__invoke();

$token = $encoder->encode(['username' => 'lexik']);
$token = $encoder->encode([$idClaim => 'lexik']);
$this->assertArrayNotHasKey('exp', $encoder->decode($token));

static::$client = static::createAuthenticatedClient($token);
Expand Down
4 changes: 4 additions & 0 deletions Tests/Functional/GetTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional;

use Lcobucci\JWT\Parser;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationSuccessResponse;
Expand Down Expand Up @@ -41,6 +42,9 @@ public function testGetTokenWithCustomClaim()

$this->assertArrayHasKey('custom', $payload, 'The payload should contains a "custom" claim.');
$this->assertSame('dummy', $payload['custom'], 'The "custom" claim should be equal to "dummy".');

$jws = (new Parser())->parse((string) $body['token']);
$this->assertArrayHasKey('foo', $jws->getHeaders(), 'The payload should contains a custom "foo" header.');
}

public function testGetTokenFromInvalidCredentials()
Expand Down
8 changes: 8 additions & 0 deletions Tests/Functional/app/config/config_user_id_claim.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
imports:
- { resource: base_config.yml }

lexik_jwt_authentication:
secret_key: '%kernel.root_dir%/../config/jwt/private.pem'
public_key: '%kernel.root_dir%/../config/jwt/public.pem'
pass_phrase: testing
user_id_claim: 'sub'
12 changes: 6 additions & 6 deletions Tests/Security/Authentication/Provider/JWTProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class JWTProviderTest extends TestCase
*/
public function testSupports()
{
$provider = new JWTProvider($this->getUserProviderMock(), $this->getJWTManagerMock(), $this->getEventDispatcherMock());
$provider = new JWTProvider($this->getUserProviderMock(), $this->getJWTManagerMock(), $this->getEventDispatcherMock(), 'username');

/** @var TokenInterface $usernamePasswordToken */
$usernamePasswordToken = $this
Expand Down Expand Up @@ -60,7 +60,7 @@ public function testAuthenticateWithInvalidJWT()
$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(false));

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

Expand All @@ -84,7 +84,7 @@ public function testAuthenticateWithoutUsername()
$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(['foo' => 'bar']));

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

Expand All @@ -109,7 +109,7 @@ public function testAuthenticateWithNotExistingUser()
$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(['username' => 'user']));

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

Expand Down Expand Up @@ -138,7 +138,7 @@ public function testAuthenticate()
$jwtManager = $this->getJWTManagerMock();
$jwtManager->expects($this->any())->method('decode')->will($this->returnValue(['username' => 'user']));

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

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

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

$this->assertInstanceOf(
Expand Down
Loading

0 comments on commit 0862239

Please sign in to comment.