Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firewall config #5

Merged
merged 5 commits into from
Apr 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ public function getConfigTreeBuilder()
->defaultValue(86400)
->cannotBeEmpty()
->end()
->scalarNode('header_prefix')
->defaultValue('Bearer')
->cannotBeEmpty()
->end()
->end();

return $treeBuilder;
Expand Down
1 change: 0 additions & 1 deletion DependencyInjection/LexikJWTAuthenticationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('lexik_jwt_authentication.public_key_path', $config['public_key_path']);
$container->setParameter('lexik_jwt_authentication.pass_phrase', $config['pass_phrase']);
$container->setParameter('lexik_jwt_authentication.token_ttl', $config['token_ttl']);
$container->setParameter('lexik_jwt_authentication.header_prefix', trim($config['header_prefix']));
}
}
64 changes: 58 additions & 6 deletions DependencyInjection/Security/Factory/JWTFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,40 @@ class JWTFactory implements SecurityFactoryInterface
*/
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.jwt.'.$id;
$providerId = 'security.authentication.provider.jwt.' . $id;
$container
->setDefinition($providerId, new DefinitionDecorator('jwt.security.authentication.provider'))
->setDefinition($providerId, new DefinitionDecorator('lexik_jwt_authentication.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider));

$listenerId = 'security.authentication.listener.jwt.'.$id;
$container->setDefinition($listenerId, new DefinitionDecorator('jwt.security.authentication.listener'));
$listenerId = 'security.authentication.listener.jwt.' . $id;
$container
->setDefinition($listenerId, new DefinitionDecorator('lexik_jwt_authentication.security.authentication.listener'));

if ($config['authorization_header']['enabled']) {

$authorizationHeaderExtractorId = 'lexik_jwt_authentication.extractor.authorization_header_extractor.' . $id;
$container
->setDefinition($authorizationHeaderExtractorId, new DefinitionDecorator('lexik_jwt_authentication.extractor.authorization_header_extractor'))
->replaceArgument(0, $config['authorization_header']['prefix']);

$container
->getDefinition($listenerId)
->addMethodCall('addTokenExtractor', array(new Reference($authorizationHeaderExtractorId)));

}

if ($config['query_parameter']['enabled']) {

$queryParameterExtractorId = 'lexik_jwt_authentication.extractor.query_parameter_extractor.' . $id;
$container
->setDefinition($queryParameterExtractorId, new DefinitionDecorator('lexik_jwt_authentication.extractor.query_parameter_extractor'))
->replaceArgument(0, $config['query_parameter']['name']);

$container
->getDefinition($listenerId)
->addMethodCall('addTokenExtractor', array(new Reference($queryParameterExtractorId)));

}

return array($providerId, $listenerId, $defaultEntryPoint);
}
Expand All @@ -44,13 +71,38 @@ public function getPosition()
*/
public function getKey()
{
return 'jwt';
return 'lexik_jwt';
}

/**
* {@inheritdoc}
*/
public function addConfiguration(NodeDefinition $builder)
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->arrayNode('authorization_header')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultTrue()
->end()
->scalarNode('prefix')
->defaultValue('Bearer')
->end()
->end()
->end()
->arrayNode('query_parameter')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultFalse()
->end()
->scalarNode('name')
->defaultValue('bearer')
->end()
->end()
->end()
->end();
}
}
2 changes: 2 additions & 0 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 Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

Expand All @@ -20,6 +21,7 @@ public function build(ContainerBuilder $container)
{
parent::build($container);

/** @var SecurityExtension $extension */
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new JWTFactory());
}
Expand Down
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,20 @@ Then in your `config.yml` :
lexik_jwt_authentication:
private_key_path: 'app/var/jwt/private.pem' # path to the private key
public_key_path: 'app/var/jwt/public.pem' # path to the public key
pass_phrase: '' # pass phrase, defaults to ''
token_ttl: 86400 # token ttl in seconds, defaults to 86400
header_prefix: 'Bearer' # authorization header value prefix, defaults to 'Bearer'
pass_phrase: '' # optional - pass phrase, defaults to ''
token_ttl: 86400 # optional - token ttl, defaults to 86400
```

Usage
-----

First of all, you need to authenticate the user using its credentials through form login or http basic. Set the `lexik_jwt_authentication.handler.authentication_success` service as success handler, which will generate the JWT token and send it as the body of a JsonResponse (along with some non-encrypted optionnal data, see example below).
First of all, you need to authenticate the user using its credentials through form login or http basic.
Set the `lexik_jwt_authentication.handler.authentication_success` service as success handler, which will generate the JWT token and send it as the body of a JsonResponse.

Store the token in your client application (using cookie, localstorage or whatever - the token is encrypted).
Now, you only need to pass the token on each future request, either as an authorization header or as a query string parameter.

Now, you only need to pass it as an Authorization header on each future request. If it results in a 401 response, your token is invalid (most likely its ttl has expired - 86400 seconds by default).

If it results in a 401 response, your token is invalid (most likely its ttl has expired - 86400 seconds by default).
Redo the authentication process to get a fresh token.

### Example of possible `security.yml` :
Expand All @@ -77,14 +77,25 @@ firewalls:
require_previous_session: false
username_parameter: username
password_parameter: password
success_handler: lexik_jwt_authentication.handler.authentication_success # sends a 200 response with the token and optionnal extra data as body
failure_handler: lexik_jwt_authentication.handler.authentication_failure # sends a 401 response
success_handler: lexik_jwt_authentication.handler.authentication_success # generate the jwt token and send it as 200 response body
failure_handler: lexik_jwt_authentication.handler.authentication_failure # send a 401 response

# protected firewall, where a user will be authenticated by its jwt token (passed as an authorization header)
# protected firewall, where a user will be authenticated by its jwt token
api:
pattern: ^/api
stateless: true
jwt: true

# default configuration
lexik_jwt: ~ # check token in Authorization Header, with a value prefix of 'Bearer'

# advanced configuration
lexik_jwt:
authorization_header: # check token in Authorization Header
enabled: true
prefix: Bearer
query_parameter: # check token in query string parameter
enabled: true
name: bearer

access_control:
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Expand Down Expand Up @@ -131,7 +142,7 @@ public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $even
}
```

### Functionnal tests (example)
### Using jwt authentication in functional tests

Generate some test specific keys, for example :

Expand All @@ -146,13 +157,12 @@ Override the bundle configuration in your `config_test.yml` :
lexik_jwt_authentication:
private_key_path: %kernel.cache_dir%/jwt/private.pem
public_key_path: %kernel.cache_dir%/jwt/jwt/public.pem
pass_phrase: 'test'
```

In your functionnal tests, create an authenticated client :
In your functional tests, create an authenticated client :

``` php
protected function createAuthenticatedClient($username = 'admin@acme.tld')
protected function createAuthenticatedClient($username = 'user@acme.tld')
{
$client = static::createClient();

Expand All @@ -169,5 +179,5 @@ protected function createAuthenticatedClient($username = '[email protected]')
TODO
----

* Add the possibility to get the token from query string parameter
* Add an optionnal IP restriction
* Add IP to encrypted paypload ?
* Add encryption algorithm option ?
19 changes: 14 additions & 5 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<parameter key="lexik_jwt_authentication.jwt_encoder.class">Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoder</parameter>
<parameter key="lexik_jwt_authentication.handler.authentication_success.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler</parameter>
<parameter key="lexik_jwt_authentication.handler.authentication_failure.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler</parameter>
<parameter key="jwt.security.authentication.provider.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider\JWTProvider</parameter>
<parameter key="jwt.security.authentication.listener.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener</parameter>
<parameter key="lexik_jwt_authentication.security.authentication.provider.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Provider\JWTProvider</parameter>
<parameter key="lexik_jwt_authentication.security.authentication.listener.class">Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener</parameter>
<parameter key="lexik_jwt_authentication.extractor.authorization_header_extractor.class">Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\AuthorizationHeaderTokenExtractor</parameter>
<parameter key="lexik_jwt_authentication.extractor.query_parameter_extractor.class">Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\QueryParameterTokenExtractor</parameter>
</parameters>

<services>
Expand All @@ -30,15 +32,22 @@
<tag name="monolog.logger" channel="security"></tag>
</service>
<!-- JWT Security Authentication Provider -->
<service id="jwt.security.authentication.provider" class="%jwt.security.authentication.provider.class%" public="false">
<service id="lexik_jwt_authentication.security.authentication.provider" class="%lexik_jwt_authentication.security.authentication.provider.class%" public="false">
<argument /> <!-- User Provider -->
<argument type="service" id="lexik_jwt_authentication.jwt_encoder" />
</service>
<!-- JWT Security Authentication Listener -->
<service id="jwt.security.authentication.listener" class="%jwt.security.authentication.listener.class%" public="false">
<service id="lexik_jwt_authentication.security.authentication.listener" class="%lexik_jwt_authentication.security.authentication.listener.class%" public="false">
<argument type="service" id="security.context"/>
<argument type="service" id="security.authentication.manager" />
<argument>%lexik_jwt_authentication.header_prefix%</argument>
</service>
<!-- Authorization Header Token Extractor -->
<service id="lexik_jwt_authentication.extractor.authorization_header_extractor" class="%lexik_jwt_authentication.extractor.authorization_header_extractor.class%" public="false">
<argument /> <!-- Header Value Prefix -->
</service>
<!-- Query Parameter Token Extractor -->
<service id="lexik_jwt_authentication.extractor.query_parameter_extractor" class="%lexik_jwt_authentication.extractor.query_parameter_extractor.class%" public="false">
<argument /> <!-- Parameter Name -->
</service>
</services>

Expand Down
39 changes: 22 additions & 17 deletions Security/Firewall/JWTListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall;

use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -29,33 +30,32 @@ class JWTListener implements ListenerInterface
protected $authenticationManager;

/**
* @var string
* @var array
*/
protected $headerPrefix;
protected $tokenExtractors;

/**
* @param SecurityContextInterface $securityContext
* @param AuthenticationManagerInterface $authenticationManager
* @param string $headerPrefix
*/
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $headerPrefix)
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
$this->headerPrefix = $headerPrefix;
$this->tokenExtractors = array();
}

/**
* {@inheritdoc}
*/
public function handle(GetResponseEvent $event)
{
if (!($raw = $this->getRawTokenFromRequest($event->getRequest()))) {
if (!($requestToken = $this->getRequestToken($event->getRequest()))) {
return;
}

$token = new JWTUserToken();
$token->setRawToken($raw);
$token->setRawToken($requestToken);

try {

Expand All @@ -73,23 +73,28 @@ public function handle(GetResponseEvent $event)
}
}

/**
* @param TokenExtractorInterface $extractor
*/
public function addTokenExtractor(TokenExtractorInterface $extractor)
{
$this->tokenExtractors[] = $extractor;
}

/**
* @param Request $request
*
* @return boolean|string
*/
protected function getRawTokenFromRequest(Request $request)
protected function getRequestToken(Request $request)
{
if (!$request->headers->has('Authorization')) {
return false;
}

$headerParts = explode(' ', $request->headers->get('Authorization'));

if (!(count($headerParts) === 2 && $headerParts[0] === $this->headerPrefix)) {
return false;
/** @var TokenExtractorInterface $tokenExtractor */
foreach ($this->tokenExtractors as $tokenExtractor) {
if (($token = $tokenExtractor->extract($request))) {
return $token;
}
}

return $headerParts[1];
return false;
}
}
Loading