Skip to content

Commit

Permalink
#177 Add JWTAuthenticationResponse (chalasr)
Browse files Browse the repository at this point in the history
| Q             | A    |
|---------------|------|
| Bug fix?      | no  |
| New feature?  | yes |
| BC breaks?    | no  |
| Deprecations | yes |
| Fixed tickets | n/a |
| Tests pass?   | yes  |

This introduces a generic Response class for successful/failed authentication.
BTW remove duplicated failure Responses with pretty much the same data (3times).

Let me know what do you think.

Steps:

- [x] Add JWTAuthenticationFailureResponse & JWTAuthenticationSuccessResponses
- [x] Add tests
- [x] Update documentation
  • Loading branch information
chalasr committed Jun 6, 2016
1 parent 12ad8eb commit 474a4d8
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 52 deletions.
21 changes: 10 additions & 11 deletions Resources/doc/2-data-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,6 @@ public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $even
return;
}

// $data['token'] contains the JWT

$data['data'] = array(
'roles' => $user->getRoles(),
);
Expand Down Expand Up @@ -199,7 +197,7 @@ public function onJwtEncoded(JWTEncodedEvent $event)

#### Events::AUTHENTICATION_FAILURE - customize the failure response

By default, the response in case of failed authentication is just a json containing a "Bad credentials" message and a 401 status code, but you can set a custom response.
By default, the response in case of failed authentication is just a json containing a failure message and a 401 status code, but you can set a custom response.

``` yaml
# services.yml
Expand All @@ -214,6 +212,9 @@ Example 7: set a custom response on authentication failure
``` php
// Acme\Bundle\ApiBundle\EventListener\AuthenticationFailureListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;

/**
* @param AuthenticationFailureEvent $event
*/
Expand All @@ -224,7 +225,7 @@ public function onAuthenticationFailureResponse(AuthenticationFailureEvent $even
'message' => 'Bad credentials, please verify that your username/password are correctly set',
];

$response = new JsonResponse($data, 401);
$response = new JWTAuthenticationFailureResponse($data);

$event->setResponse($response);
}
Expand All @@ -243,21 +244,19 @@ services:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_invalid, method: onJWTInvalid }
```
Example 8: set a custom response message on invalid token
Example 8: set a custom response message and status code on invalid token
``` php
// Acme\Bundle\ApiBundle\EventListener\JWTInvalidListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;

/**
* @param JWTInvalidEvent $event
*/
public function onJWTInvalid(JWTInvalidEvent $event)
{
$data = [
'status' => '403 Forbidden',
'message' => 'Your token is invalid, please login again to get a new one',
];

$response = new JsonResponse($data, 403);
$response = new JWTAuthenticationFailureResponse('Your token is invalid, please login again to get a new one', 403);

$event->setResponse($response);
}
Expand Down
70 changes: 70 additions & 0 deletions Response/JWTAuthenticationFailureResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Response;

use Symfony\Component\HttpFoundation\JsonResponse;

/**
* JWTAuthenticationFailureResponse.
*
* Response sent on failed JWT authentication (can be replaced by a custom Response).
*
* @internal
*
* @author Robin Chalas <[email protected]>
*/
final class JWTAuthenticationFailureResponse extends JsonResponse
{
/**
* The response message.
*
* @var string
*/
private $message;

/**
* @param string $message A failure message passed in the response body
*/

This comment has been minimized.

Copy link
@chalasr

chalasr Jun 8, 2016

Author Collaborator

Missing statusCode phpdoc...

public function __construct($message = 'Bad credentials', $statusCode = JsonResponse::HTTP_UNAUTHORIZED)
{
$this->message = $message;

parent::__construct(null, $statusCode, ['WWW-Authenticate' => 'Bearer']);
}

/**
* Sets the failure message.
*
* @param string $message
*
* @return JWTAuthenticationFailureResponse
*/
public function setMessage($message)
{
$this->message = $message;

$this->setData();

return $this;
}

/**
* Gets the failure message.
*
* @return string
*/
public function getMessage()
{
return $this->message;
}

/**
* Sets the response data with the statusCode & message included.
*
* {@inheritdoc}
*/
public function setData($data = [])
{
parent::setData(['code' => $this->statusCode, 'message' => $this->message] + (array) $data);
}
}
45 changes: 45 additions & 0 deletions Response/JWTAuthenticationSuccessResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Response;

use Symfony\Component\HttpFoundation\JsonResponse;

/**
* Response sent on successful JWT authentication.
*
* @internal
*
* @author Robin Chalas <[email protected]>
*/
final class JWTAuthenticationSuccessResponse extends JsonResponse
{
/**
* The Json Web Token.
*
* Immutable property.
*
* @var string
*/
private $token;

/**
* @param string $token Json Web Token
* @param array $data Extra data passed to the response.
*/
public function __construct($token, array $data = null)
{
$this->token = $token;

parent::__construct($data);
}

/**
* Sets the response data with the JWT included.
*
* {@inheritdoc}
*/
public function setData($data = [])
{
parent::setData(['token' => $this->token] + (array) $data);
}
}
10 changes: 2 additions & 8 deletions Security/Firewall/JWTListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
Expand Down Expand Up @@ -91,13 +91,7 @@ public function handle(GetResponseEvent $event)
throw $failed;
}

$data = [
'code' => 401,
'message' => $failed->getMessage(),
];

$response = new JsonResponse($data, $data['code']);
$response->headers->set('WWW-Authenticate', 'Bearer');
$response = new JWTAuthenticationFailureResponse($failed->getMessage());

$jwtInvalidEvent = new JWTInvalidEvent($request, $failed, $response);
$this->dispatcher->dispatch(Events::JWT_INVALID, $jwtInvalidEvent);
Expand Down
13 changes: 2 additions & 11 deletions Security/Http/Authentication/AuthenticationFailureHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
Expand All @@ -17,9 +17,6 @@
*/
class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
{
const RESPONSE_CODE = 401;
const RESPONSE_MESSAGE = 'Bad credentials';

/**
* @var EventDispatcherInterface
*/
Expand All @@ -38,13 +35,7 @@ public function __construct(EventDispatcherInterface $dispatcher)
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
'code' => self::RESPONSE_CODE,
'message' => self::RESPONSE_MESSAGE,
];

$response = new JsonResponse($data, self::RESPONSE_CODE);
$event = new AuthenticationFailureEvent($request, $exception, $response);
$event = new AuthenticationFailureEvent($request, $exception, new JWTAuthenticationFailureResponse());

$this->dispatcher->dispatch(Events::AUTHENTICATION_FAILURE, $event);

Expand Down
9 changes: 4 additions & 5 deletions Security/Http/Authentication/AuthenticationSuccessHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTManager;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationSuccessResponse;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
Expand Down Expand Up @@ -43,10 +43,9 @@ public function __construct(JWTManager $jwtManager, EventDispatcherInterface $di
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$user = $token->getUser();
$jwt = $this->jwtManager->create($user);

$response = new JsonResponse();
$user = $token->getUser();
$jwt = $this->jwtManager->create($user);
$response = new JWTAuthenticationSuccessResponse($jwt);
$event = new AuthenticationSuccessEvent(['token' => $jwt], $user, $request, $response);

$this->dispatcher->dispatch(Events::AUTHENTICATION_SUCCESS, $event);
Expand Down
12 changes: 2 additions & 10 deletions Security/Http/EntryPoint/JWTEntryPoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Http\EntryPoint;

use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

Expand All @@ -19,15 +19,7 @@ class JWTEntryPoint implements AuthenticationEntryPointInterface
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$statusCode = 401;

$data = [
'code' => $statusCode,
'message' => 'Invalid credentials',
];

$response = new JsonResponse($data, $statusCode);
$response->headers->set('WWW-Authenticate', 'Bearer');
$response = new JWTAuthenticationFailureResponse();

return $response;
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/Functional/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ framework:
resource: %kernel.root_dir%/config/routing.yml

lexik_jwt_authentication:
private_key_path: %kernel.root_dir%/var/private.pem
public_key_path: %kernel.root_dir%/var/public.pem
private_key_path: '%kernel.root_dir%/var/private.pem'
public_key_path: '%kernel.root_dir%/var/public.pem'
pass_phrase: testing

security:
Expand Down
45 changes: 45 additions & 0 deletions Tests/Response/JWTAuthenticationFailureResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Response;

use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;

/**
* Tests the JWTAuthenticationFailureResponse
*
* @author Robin Chalas <[email protected]>
*/
final class JWTAuthenticationFailureResponseTest extends \PHPUnit_Framework_TestCase
{
public function testResponse()
{
$expected = [
'code' => 401,
'message' => 'message',
];

$response = new JWTAuthenticationFailureResponse($expected['message']);

$this->assertSame($expected['message'], $response->getMessage());
$this->assertSame($expected['code'], $response->getStatusCode());
$this->assertSame('Bearer', $response->headers->get('WWW-Authenticate'));
$this->assertSame(json_encode($expected), $response->getContent());

return $response;
}

/**
* @depends testResponse
*/
public function testSetMessage(JWTAuthenticationFailureResponse $response)
{
$newMessage = 'new message';
$response->setMessage($newMessage);

$responseBody = json_decode($response->getContent());

$this->assertSame($response->getStatusCode(), $responseBody->code);
$this->assertSame($newMessage, $response->getMessage());
$this->assertSame($newMessage, $responseBody->message);
}
}
42 changes: 42 additions & 0 deletions Tests/Response/JWTAuthenticationSuccessResponseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Response;

use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationSuccessResponse;

/**
* Tests the JWTAuthenticationSuccessResponse.
*
* @author Robin Chalas <[email protected]>
*/
final class JWTAuthenticationSuccessResponseTest extends \PHPUnit_Framework_TestCase
{
public function testResponse()
{
$data = [
'username' => 'foobar',
'email' => '[email protected]'
];
$expected = ['token' => 'jwt'] + $data;
$response = new JWTAuthenticationSuccessResponse($expected['token'], $data);

$this->assertSame(200, $response->getStatusCode());
$this->assertSame(json_encode($expected), $response->getContent());

return $response;
}

/**
* @depends testResponse
*/
public function testReplaceData(JWTAuthenticationSuccessResponse $response)
{
$replacementData = ['foo' => 'bar'];
$response->setData($replacementData);

// Test that the previous method call has no effect on the original body
$this->assertNotEquals(json_encode($replacementData), $response->getContent());
$this->assertAttributeSame($replacementData['foo'], 'foo', json_decode($response->getContent()));
$this->assertAttributeNotEmpty('token', json_decode($response->getContent()));
}
}
Loading

0 comments on commit 474a4d8

Please sign in to comment.