diff --git a/Resources/doc/2-data-customization.md b/Resources/doc/2-data-customization.md index b777c97c..b9beecfb 100644 --- a/Resources/doc/2-data-customization.md +++ b/Resources/doc/2-data-customization.md @@ -169,8 +169,6 @@ public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $even return; } - // $data['token'] contains the JWT - $data['data'] = array( 'roles' => $user->getRoles(), ); @@ -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 @@ -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 */ @@ -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); } @@ -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); } diff --git a/Response/JWTAuthenticationFailureResponse.php b/Response/JWTAuthenticationFailureResponse.php new file mode 100644 index 00000000..7ba50c83 --- /dev/null +++ b/Response/JWTAuthenticationFailureResponse.php @@ -0,0 +1,70 @@ + + */ +final class JWTAuthenticationFailureResponse extends JsonResponse +{ + /** + * The response message. + * + * @var string + */ + private $message; + + /** + * @param string $message A failure message passed in the response body + */ + 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); + } +} diff --git a/Response/JWTAuthenticationSuccessResponse.php b/Response/JWTAuthenticationSuccessResponse.php new file mode 100644 index 00000000..8ff8cbe7 --- /dev/null +++ b/Response/JWTAuthenticationSuccessResponse.php @@ -0,0 +1,45 @@ + + */ +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); + } +} diff --git a/Security/Firewall/JWTListener.php b/Security/Firewall/JWTListener.php index 39d56d0b..ccc85a0d 100644 --- a/Security/Firewall/JWTListener.php +++ b/Security/Firewall/JWTListener.php @@ -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; @@ -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); diff --git a/Security/Http/Authentication/AuthenticationFailureHandler.php b/Security/Http/Authentication/AuthenticationFailureHandler.php index 9aaf0fc9..f1182711 100644 --- a/Security/Http/Authentication/AuthenticationFailureHandler.php +++ b/Security/Http/Authentication/AuthenticationFailureHandler.php @@ -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; @@ -17,9 +17,6 @@ */ class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface { - const RESPONSE_CODE = 401; - const RESPONSE_MESSAGE = 'Bad credentials'; - /** * @var EventDispatcherInterface */ @@ -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); diff --git a/Security/Http/Authentication/AuthenticationSuccessHandler.php b/Security/Http/Authentication/AuthenticationSuccessHandler.php index df4fdb8d..6b904f2d 100644 --- a/Security/Http/Authentication/AuthenticationSuccessHandler.php +++ b/Security/Http/Authentication/AuthenticationSuccessHandler.php @@ -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; @@ -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); diff --git a/Security/Http/EntryPoint/JWTEntryPoint.php b/Security/Http/EntryPoint/JWTEntryPoint.php index a1e116d0..9cc12c43 100644 --- a/Security/Http/EntryPoint/JWTEntryPoint.php +++ b/Security/Http/EntryPoint/JWTEntryPoint.php @@ -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; @@ -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; } diff --git a/Tests/Functional/config/config.yml b/Tests/Functional/config/config.yml index 20718045..bbc4eecf 100644 --- a/Tests/Functional/config/config.yml +++ b/Tests/Functional/config/config.yml @@ -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: diff --git a/Tests/Response/JWTAuthenticationFailureResponseTest.php b/Tests/Response/JWTAuthenticationFailureResponseTest.php new file mode 100644 index 00000000..44b4c89e --- /dev/null +++ b/Tests/Response/JWTAuthenticationFailureResponseTest.php @@ -0,0 +1,45 @@ + + */ +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); + } +} diff --git a/Tests/Response/JWTAuthenticationSuccessResponseTest.php b/Tests/Response/JWTAuthenticationSuccessResponseTest.php new file mode 100644 index 00000000..045e3f82 --- /dev/null +++ b/Tests/Response/JWTAuthenticationSuccessResponseTest.php @@ -0,0 +1,42 @@ + + */ +final class JWTAuthenticationSuccessResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testResponse() + { + $data = [ + 'username' => 'foobar', + 'email' => 'dev@lexik.fr' + ]; + $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())); + } +} diff --git a/Tests/Security/Authentication/Firewall/JWTListenerTest.php b/Tests/Security/Authentication/Firewall/JWTListenerTest.php index 2fc4d674..fa69aa26 100644 --- a/Tests/Security/Authentication/Firewall/JWTListenerTest.php +++ b/Tests/Security/Authentication/Firewall/JWTListenerTest.php @@ -3,6 +3,7 @@ namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Security\Authentication\Firewall; use Lexik\Bundle\JWTAuthenticationBundle\Security\Firewall\JWTListener; +use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse; /** * JWTListenerTest @@ -60,10 +61,7 @@ public function testHandle() $event ->expects($this->once()) ->method('setResponse') - ->with(new \Symfony\Component\HttpFoundation\JsonResponse([ - 'code' => 401, - 'message' => $invalidTokenException->getMessage(), - ], 401, ['WWW-Authenticate' => 'Bearer'])); + ->with(new JWTAuthenticationFailureResponse($invalidTokenException->getMessage())); $listener->handle($event); } diff --git a/Tests/Security/Http/EntryPoint/JWTEntryPointTest.php b/Tests/Security/Http/EntryPoint/JWTEntryPointTest.php index 28caf527..3c3af67b 100644 --- a/Tests/Security/Http/EntryPoint/JWTEntryPointTest.php +++ b/Tests/Security/Http/EntryPoint/JWTEntryPointTest.php @@ -24,7 +24,7 @@ public function testStart() $data = json_decode($response->getContent(), true); $this->assertEquals($data['code'], '401'); - $this->assertEquals($data['message'], 'Invalid credentials'); + $this->assertEquals($data['message'], 'Bad credentials'); } /**