Skip to content

Commit

Permalink
POC
Browse files Browse the repository at this point in the history
  • Loading branch information
chalasr committed Dec 2, 2016
1 parent 7e21ffe commit 1ee6695
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 18 deletions.
46 changes: 46 additions & 0 deletions DependencyInjection/Security/Factory/JWTUserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory;

use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUser;
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;

/**
* JWTUserFactory.
*
* @author Robin Chalas <[email protected]>
*/
class JWTUserFactory implements UserProviderFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config)
{
$definition = $container->setDefinition($id, new DefinitionDecorator('lexik_jwt_authentication.security.jwt_user_provider'));
$definition->replaceArgument(0, $config['class']);
}

public function getKey()
{
return 'lexik_jwt';
}

public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode('class')
->cannotBeEmpty()
->defaultValue(JWTUser::class)
->validate()
->ifTrue(function ($class) { return !is_subclass_of($class, JWTUserInterface::class); })
->thenInvalid('The %s class must implement JWTUserInterface.')
->end()
->end()
->end()
;
}
}
5 changes: 3 additions & 2 deletions LexikJWTAuthenticationBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Lexik\Bundle\JWTAuthenticationBundle;

use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTFactory;
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory\JWTUserFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
Expand All @@ -24,7 +25,7 @@ public function build(ContainerBuilder $container)
/** @var SecurityExtension $extension */
$extension = $container->getExtension('security');

// BC 1.x, to be removed in 3.0
$extension->addSecurityListenerFactory(new JWTFactory());
$extension->addUserProviderFactory(new JWTUserFactory());
$extension->addSecurityListenerFactory(new JWTFactory()); // BC 1.x, to be removed in 3.0
}
}
4 changes: 4 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
<service id="lexik_jwt_authentication.extractor.cookie_extractor" class="Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\CookieTokenExtractor">
<argument /> <!-- Name -->
</service>
<!-- JWT User Provider -->
<service public="false" id="lexik_jwt_authentication.security.jwt_user_provider" class="Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserProvider">
<argument />
</service>
</services>

</container>
38 changes: 24 additions & 14 deletions Security/Guard/JWTTokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserProvider;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -135,20 +136,8 @@ public function getUser($preAuthToken, UserProviderInterface $userProvider)
);
}

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

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

$identity = $payload[$identityField];

try {
$user = $userProvider->loadUserByUsername($identity);
} catch (UsernameNotFoundException $e) {
throw new UserNotFoundException($identityField, $identity);
}
$payload = $preAuthToken->getPayload();
$user = $this->findUser($userProvider, $payload);

$this->preAuthenticationTokenStorage->setToken($preAuthToken);

Expand Down Expand Up @@ -246,4 +235,25 @@ protected function getTokenExtractor()
{
return $this->tokenExtractor;
}

private function findUser(UserProviderInterface $userProvider, array $payload)
{
$identityField = $this->jwtManager->getUserIdentityField();

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

$identity = $payload[$identityField];

if ($userProvider instanceof JWTUserProvider) {
return $userProvider->loadUserByUsername($identity, $payload);
}

try {
$user = $userProvider->loadUserByUsername($identity);
} catch (UsernameNotFoundException $e) {
throw new UserNotFoundException($identityField, $identity);
}
}
}
69 changes: 69 additions & 0 deletions Security/User/JWTUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;

class JWTUser implements JWTUserInterface
{
private $username;
private $roles;

public function __construct($username, array $roles = [])
{
$this->username = $username;
$this->roles = $roles;
}

/**
* Creates a new instance from a given JWT payload.
*
* @param string $username
* @param array $payload
*
* @return static
*/
public static function createFromPayload($username, array $payload)
{
if (isset($payload['roles'])) {
return new self($username, (array) $payload['roles']);
}

return new self($username);
}

/**
* {@inheritdoc}
*/
public function getUsername()
{
return $this->username;
}

/**
* {@inheritdoc}
*/
public function getRoles()
{
return $this->roles;
}

/**
* {@inheritdoc}
*/
public function getPassword()
{
}

/**
* {@inheritdoc}
*/
public function getSalt()
{
}

/**
* {@inheritdoc}
*/
public function eraseCredentials()
{
}
}
18 changes: 18 additions & 0 deletions Security/User/JWTUserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;

use Symfony\Component\Security\Core\User\UserInterface;

interface JWTUserInterface extends UserInterface
{
/**
* Creates a new instance from a given JWT payload.
*
* @param string $username
* @param array $payload
*
* @return JWTUserInterface
*/
public static function createFromPayload($username, array $payload);
}
57 changes: 57 additions & 0 deletions Security/User/JWTUserProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

/**
* JWT User provider.
*
* @author Robin Chalas <[email protected]>
*/
final class JWTUserProvider implements UserProviderInterface
{
private $class;

// FIXME
private $cache;

/**
* @param string $class The {@link JWTUserInterface} implementation FQCN for which to provide instances.
*/
public function __construct($class)
{
$this->class = $class;
}

/**
* {@inheritdoc}
*
* FIXME
*/
public function loadUserByUsername($username, array $payload = null)
{
if (null === $payload) {
return;
}

$class = $this->class;

return $class::createFromPayload($username, $payload);
}

/**
* {@inheritdoc}
*/
public function supportsClass($class)
{
return $class === $this->class || is_subclass_of($class, $this->class);
}

public function refreshUser(UserInterface $user)
{
// no datastore => no refresh
// only one JWT and the corresponding instances
}
}
8 changes: 6 additions & 2 deletions Tests/Functional/Bundle/Controller/TestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional\Bundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;

class TestController extends Controller
{
public function securedAction()
{
return new Response();
return new JsonResponse([
'class' => get_class($this->getUser()),
'roles' => $this->getUser()->getRoles(),
'username' => $this->getUser()->getUsername()
]);
}

public function loginCheckAction()
Expand Down
1 change: 1 addition & 0 deletions Tests/Functional/CompleteTokenAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public function testAccessSecuredRoute()

$this->assertSuccessful($response);

// FIXME perform some assertions
return json_decode($response->getContent(), true);
}

Expand Down
4 changes: 4 additions & 0 deletions Tests/Functional/app/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ security:
lexik:
password: dummy
roles: ROLE_USER
jwt:
lexik_jwt: ~

firewalls:
login:
pattern: ^/login
stateless: true
anonymous: true
provider: in_memory
form_login:
check_path: /login_check
require_previous_session: false
Expand All @@ -33,6 +36,7 @@ security:
pattern: ^/api
stateless: true
anonymous: false
provider: jwt
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
Expand Down

0 comments on commit 1ee6695

Please sign in to comment.