Reputation: 725
Using Symfony2 I have implemented an AJAX action to manage some bookmarks (add/remove) in my application. So a user needs to be authenticated to proceed. I have a solution that redirects user to login page but I think it would be better to use an event to handle this redirection.
Actual solution :
Check of user's authentication is done the same way that in FOSUserBundle.
Routing :
fbn_guide_manage_bookmark:
path: /bookmark/manage
defaults: { _controller: FBNGuideBundle:Guide:managebookmark }
options:
expose: true
requirements:
_method: POST
Controller :
public function manageBookmarkAction(Request $request)
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
return new JsonResponse(array('status' => 'login'));
}
// DO THE STUFF
}
}
jQuery :
$(function() {
$('#bookmark').click(function() {
$.ajax({
type: 'POST',
url: Routing.generate('fbn_guide_manage_bookmark'),
data : xxxx, // SOME DATA
success: function(data) {
if (data.status == 'login') {
var redirect = Routing.generate('fos_user_security_login');
window.location.replace(redirect);
} else {
// DO THE STUFF
}
},
});
});
});
Other solution ? :
In order not verify at controller level that user is authenticated, I would protect my route in security configuration file :
Security :
security:
access_control:
- { path: ^/(fr|en)/bookmark/manage, role: ROLE_USER }
Controller :
public function manageBookmarkAction(Request $request)
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
// THIS VERIFCATION SHOULD NOW BE REMOVED
/*
if (!is_object($user) || !$user instanceof UserInterface) {
return new JsonResponse(array('status' => 'login'));
}
*/
// DO THE STUFF
}
}
Basically, when trying this solution, Symfony2 redirects internally ton login page as you can see with Firebug :
So my questions are :
I could see some exception event solution based but I think it is necessary to throw the exception at controller level and this is what I would like to avoid. Here is an example :
Upvotes: 2
Views: 6094
Reputation: 1
I solved this for Symf4 (shouldn't be very different from others). The exception listener will provide JSON response for the POST before redirect happens. In other cases it will still redirect as usual. You can customize further how to handle exceptions in the listener.
=======================================================
sevices:
exeption_listener:
class: Path\To\Listener\ExeptionListener
arguments: ['@security.token_storage']
tags:
- { name: kernel.event_listener, event: kernel.exception }
=======================================================
Listener/ExeptionListener.php
<?php
namespace Tensor\UserBundle\Listener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
class ExeptionListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return array(
KernelEvents::EXCEPTION => array(
array('processException', 10),
array('logException', 0),
array('notifyException', -10),
)
);
}
public function processException(GetResponseForExceptionEvent $event)
{
// ...
if (!$event->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
$request = $event->getRequest();
if( $request->getMethod() === 'POST' ){
$event->setResponse(new JsonResponse(array('error'=>$event->getException()->getMessage()), 403));
}
}
public function logException(GetResponseForExceptionEvent $event)
{
// ...
}
public function notifyException(GetResponseForExceptionEvent $event)
{
// ...
}
}
Upvotes: 0
Reputation: 725
Here is a solution (see here for details) :
Security :
firewalls:
main:
pattern: ^/
anonymous: true
provider: fos_userbundle
entry_point: fbn_user.login_entry_point
#...
access_control:
- { path: ^/(fr|en)/bookmark/manage, role: ROLE_USER }
Services :
services:
fbn_user.login_entry_point:
class: FBN\UserBundle\EventListener\LoginEntryPoint
arguments: [ @router ]
Service class :
namespace FBN\UserBundle\EventListener;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* When the user is not authenticated at all (i.e. when the security context has no token yet),
* the firewall's entry point will be called to start() the authentication process.
*/
class LoginEntryPoint implements AuthenticationEntryPointInterface
{
protected $router;
public function __construct($router)
{
$this->router = $router;
}
/**
* This method receives the current Request object and the exception by which the exception
* listener was triggered.
*
* The method should return a Response object
*/
public function start(Request $request, AuthenticationException $authException = null)
{
if ($request->isXmlHttpRequest()) {
return new JsonResponse('',401);
}
return new RedirectResponse($this->router->generate('fos_user_security_login'));
}
}
jQuery :
$(function() {
$('#bookmark').click(function() {
// DATA PROCESSING
$.ajax({
type: 'POST',
url: Routing.generate('fbn_guide_manage_bookmark'),
data : xxxx, // SOME DATA,
success: function(data) {
// DO THE STUFF
},
error: function(jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
case 401:
var redirectUrl = Routing.generate('fos_user_security_login');
window.location.replace(redirectUrl);
break;
case 403: // (Invalid CSRF token for example)
// Reload page from server
window.location.reload(true);
}
},
});
});
});
Upvotes: 11
Reputation: 3523
Yes, the event can be handled as described in this answer: https://stackoverflow.com/a/9182954/982075
Use HTTP Status Code 401 (Unauthorized) or 403 (Forbidden)
You can use the error
function in jquery to handle the response
$.ajax({
type: 'POST',
url: Routing.generate('fbn_guide_manage_bookmark'),
data : xxxx, // SOME DATA
error: function() {
alert("Your session has expired");
}
});
Upvotes: 1