-
-
Notifications
You must be signed in to change notification settings - Fork 614
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add JWTUserProvider for loading users from the JWT itself
Rethink test config loading
- Loading branch information
Showing
27 changed files
with
498 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Security\Factory; | ||
|
||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUser; | ||
use Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUserInterface; | ||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; | ||
use Symfony\Component\Config\Definition\Builder\NodeDefinition; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\DefinitionDecorator; | ||
|
||
/** | ||
* Creates the `lexik_jwt` user provider. | ||
* | ||
* @internal | ||
* | ||
* @author Robin Chalas <[email protected]> | ||
*/ | ||
final 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 !(new \ReflectionClass($class))->implementsInterface(JWTUserInterface::class); | ||
}) | ||
->thenInvalid('The %s class must implement '.JWTUserInterface::class.' for using the "lexik_jwt" user provider.') | ||
->end() | ||
->end() | ||
->end() | ||
; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
A database-less user provider | ||
============================= | ||
|
||
From [jwt.io](https://jwt.io/introduction): | ||
|
||
> Self-contained: The payload contains all the required information about the user, avoiding the need to query the database more than once. | ||
> https://jwt.io/introduction | ||
A JWT is _self-contained_, meaning that we can trust into its payload for processing the authentication. | ||
In a nutshell, there should be no need for loading the user from the database when authenticating a JWT Token, | ||
the database should be hit only once for delivering the token. | ||
|
||
That's why we decided to provide an user provider which is able to create User instances from the JWT payload. | ||
|
||
Configuring the user provider | ||
----------------------------- | ||
|
||
To work, the provider just needs a few lines of configuration: | ||
|
||
```yaml | ||
# app/config/security.yml | ||
security: | ||
providers: | ||
jwt: | ||
lexik_jwt: ~ | ||
``` | ||
What does it change? | ||
-------------------- | ||
Now that the provider is configured, it will automatically be used by the `JWTGuardAuthenticator` at the time to authenticate a token. | ||
Instead of loading the user from a "datastore" (i.e. memory or any database engine), a `JWTUserInterface` instance will be created from the JWT payload, will be cached for a request and be authenticated. | ||
We provide a simple `JWTUser` class implementing this interface, which is used by default when configuring the provider. | ||
|
||
Can I use my own user class? | ||
---------------------------- | ||
|
||
Of course, you can. You just need to make your user class implement the `JWTUserInterface` interface. | ||
This interface contains only a `createFromPayload()` _named constructor_ which takes the user's username and | ||
the JWT token payload as arguments and returns an instance of the class. | ||
|
||
##### Example of implementation | ||
|
||
```php | ||
namespace AppBundle\Security; | ||
final class User implements JWTUserInterface | ||
{ | ||
// Your own logic | ||
public function __construct($username, array $roles, $email) | ||
{ | ||
$this->username = $username; | ||
$this->roles = $roles; | ||
$this->email = $email; | ||
} | ||
public static function createFromPayload($username, array $payload) | ||
{ | ||
return new self( | ||
$username, | ||
$payload['roles'] // Added by default | ||
$payload['email'] // Custom | ||
); | ||
} | ||
} | ||
``` | ||
|
||
_Note_: You can extend the default `JWTUser` class if that fits your needs. | ||
|
||
##### Configuration | ||
|
||
```yaml | ||
# app/config/security.yml | ||
providers: | ||
# ... | ||
jwt: | ||
lexik_jwt: | ||
class: AppBundle\Security\User | ||
``` | ||
|
||
And voilà! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User; | ||
|
||
/** | ||
* User class for which to create instances from JWT tokens. | ||
* | ||
* Note: This is only useful when using the JWTUserProvider (database-less). | ||
* | ||
* @author Robin Chalas <[email protected]> | ||
*/ | ||
class JWTUser implements JWTUserInterface | ||
{ | ||
private $username; | ||
private $roles; | ||
|
||
public function __construct($username, array $roles = []) | ||
{ | ||
$this->username = $username; | ||
$this->roles = $roles; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
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() | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
namespace Lexik\Bundle\JWTAuthenticationBundle\Security\User; | ||
|
||
use Symfony\Component\Security\Core\User\UserInterface; | ||
use Symfony\Component\Security\Core\User\UserProviderInterface; | ||
|
||
/** | ||
* JWT User provider. | ||
* | ||
* @author Robin Chalas <[email protected]> | ||
*/ | ||
final class JWTUserProvider implements UserProviderInterface | ||
{ | ||
private $class; | ||
private $cache = []; | ||
|
||
/** | ||
* @param string $class The {@link JWTUserInterface} implementation FQCN for which to provide instances | ||
*/ | ||
public function __construct($class) | ||
{ | ||
$this->class = $class; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @param array $payload The JWT payload from which to create an instance | ||
* | ||
* @return JWTUserInterface | ||
*/ | ||
public function loadUserByUsername($username, array $payload = []) | ||
{ | ||
$class = $this->class; | ||
|
||
if (isset($this->cache[$username])) { | ||
return $this->cache[$username]; | ||
} | ||
|
||
return $this->cache[$username] = $class::createFromPayload($username, $payload); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function supportsClass($class) | ||
{ | ||
return $class === $this->class || (new \ReflectionClass($class))->implementsInterface(JWTUserInterface::class); | ||
} | ||
|
||
public function refreshUser(UserInterface $user) | ||
{ | ||
// noop | ||
} | ||
} |
Oops, something went wrong.