Reputation: 857
In my Symfony 3 app I made so, that if the user is inactive for some time, it is logged out and requested to login again. This is done with the following code:
//RequestListener.php
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST != $event->getRequestType()) {
return;
}
if ($this->maxIdleTime > 0) {
$lapse = time() - $this->session->getMetadataBag()->getCreated();
$lapse_short = time() - $this->session->getMetadataBag()->getLastUsed();
if ($lapse >= $this->maxIdleTime || $lapse_short >= $this->shortIdleTime) {
$username = $this->securityToken->getToken()->getUser();
if ($username !== 'anon.'){
$this->session->invalidate();
$this->securityToken->setToken(null);
$event->setResponse(new RedirectResponse($this->router->generate('login')));
}
}
}
}
But in ths case redirect to login form is happened when the page is reloaded. I also want to force redirect on every ajax call also. By default my ajax calls are served by the following address: /ajax
But when the session is expired the ajax is 'redirected' to my login page address and in browsers Network tab I see the following:
My ajax function which is supposed to redirect is as follows:
function requestAjax(json_data, url) {
if(url.indexOf('login') !== -1){
window.location = './login';
}
var request = $.ajax({
url: root + '/' + url
, method: 'post'
, data: json_data
});
return request;
}
But no redirect is happened. So The question is how to force redirect on expired sessions and ajax calls and also why ajax status is 200 but not say 302 in this case? Thank you
UPD_1 My services.yml
for RequestListener.php
app.handler.session_idle:
class: AppBundle\EventListener\RequestListener
arguments: ["@session", "@security.token_storage", "@router", "@app.logger", "%session_lifetime%", "%session_active_lifetime%", "%instance_path%"]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Upvotes: 1
Views: 2539
Reputation: 3216
Have been researching on this care for quite some hours. In the symfony 5 How to Customize Access Denied Responses docs, you can customize one of the following:
1. App entry point
2. Access denied handler
3. All Access Denied Responses
Going with customizing All Access Denied Responses
, i created a kernel.exception subscriber/listener:
namespace App\EventSubscribers;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class AccessDeniedHandler implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
// the priority must be greater than the Security HTTP
// ExceptionListener, to make sure it's called before
// the default exception listener
KernelEvents::EXCEPTION => ['onKernelException', 2]
];
}
public function onKernelException(ExceptionEvent $event): void
{
// Ajax is returning login page instead of session expired/access denied
// Creating a custom handler for ajax
// more at https://symfony.com/doc/current/security/access_denied_handler.html#customize-the-unauthorized-response
$request = $event->getRequest();
if($request->isXmlHttpRequest()){
$event->setResponse(new Response('Your session has expired!', 403));
return;
}
}
}
Upvotes: 0
Reputation: 1305
You could try something like this (tested and working in Symfony 2.8)
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class AjaxAuthenticationListener {
/*
* function onCoreException
* Check if session is expired and handles security related exceptions
* @param GetResponseForExceptionEvent $event An GetResponseForExceptionEvent instance
*
*/
public function onCoreException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$event_request = $event->getRequest();
$session = $event->getRequest()->getSession();
if ($event_request->isXmlHttpRequest()) {
if ($exception instanceof AuthenticationException || $exception instanceof AccessDeniedException) {
$session->getFlashBag()->add('warning', 'You have been signed out automatically due to inactivity.');
$event->setResponse(new Response('Session expired', 403));
}
}
}
}
As you can see, "onCoreException" function returns a 403 status code.
Now, in home page (in my case) or page where you will have ajax calls, you could use "ajaxError" and catch the jqXHR.status, if it is 403, then redirect to login page and using a "FlashBag" to display a message related to expired session.
$(document).ready(function () {
//Catch AjaxAuthenticationListener response
$(document).ajaxError(function (event, jqXHR) {
if (403 === jqXHR.status) {
$(location).attr('href', '{{ path('login') }}');
}
});
I have omitted explain how "onCoreException" function works as a service and how it handles the session when it has been expired, taking into account that this part is working properly in your code.
services.yml:
app.gt.ajax.authentication.listener:
class: AppBundle\EventListener\AjaxAuthenticationListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onCoreException, priority: 1000 }
I hope this is useful for you.
Upvotes: 1