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

Add JWTAuthenticationResponse #177

Merged
merged 2 commits into from
Jun 6, 2016
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
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
*/
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that the JSON body keep the initial message when the message is modified ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @GromNaN


$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 = [])
Copy link

@GromNaN GromNaN Jun 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set data is completely ignored.
The extraData property could be removed.

public function __construct($token, $data = null, $status = 200, array $headers = array())
{
    $this->token = $token;
    parent::__construct($data, $status, $headers);
}

public function setData($data) {
    parent::setData(['token' => $this->token] + $data);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow... I was gone to far, written too late in the night... This will be widely sufficient.

{
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