How to stop Symfony redirecting after logout - php

The default Symfony behavior is to redirect to '/' after logout.
I don't require any redirects from Symfony as it's an api app.
Like how during login when Symfony takes control to do authentication, but then still runs the login controller to perform further actions. This would be ideal for logout in this case also.
security.yaml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
json_login:
check_path: app_login
username_path: email
password_path: password
logout:
path: app_logout
src/Controller/SecurityController.php from Symfony docs
/**
* #Route("/logout", name="app_logout", methods={"GET"})
*/
public function logout(): void
{
// controller can be blank: it will never be called!
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}

You can write a custom logout handler. Actually, there is a new approach for this, introduced in symfony 5.1. Basically, now you can register an event listener either globally or for some specific firewall, and perform any actions after the person has logged out.
Returning to your problem (from the blog post below):
The Symfony\Component\Security\Http\Event\LogoutEvent object
received by the listener contains useful methods such as getToken()
(to get the security token of the session), getRequest() and
setResponse().
The later will help you. It means you can return anything you want instead of default RedirectResponse by setting new response object to the event.
services.yaml:
services:
App\EventListener\CustomLogoutListener:
tags:
- name: 'kernel.event_listener'
event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
dispatcher: security.event_dispatcher.main
method: onLogout
And your listener:
namespace App\EventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class CustomLogoutListener
{
public function onLogout(LogoutEvent $logoutEvent): void
{
$logoutEvent->setResponse(new JsonResponse([]));
}
}
Read more: Simpler logout customization

Related

How catch login/logut events for programming a register (logs files) on login/logout in Symfomy 3?

I'll try to be as descriptive as possible, I'm just a rookie with Symfony.
I am maintaining a report application in symfony, right now I need to create and store in a txt file details of user accesses to the report: names, date, time, ip, time login, time logout.
My problem is that I not have clear how to program during login and logout events. In other words, how can cath the login and logout events?
Following the Symfony documentation, create a very simple login module.
obs. I have only a Bundle: myBundle.
Creating firewall in security.yml
#This content in Security.yml
encoders:
Symfony\Component\Security\Core\User\UserInterface: plaintext
providers:
our_db_provider:
entity:
class: myBundle:myUserClass
property: username
firewalls:
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /login
check_path: /login_check
username_parameter: _username
password_parameter: _password
logout:
path: /logout
target: /login
Declaring routes in routing.yml
//These routes are declared in routing.yml (in the Bundle:myBundle)
login:
path: /login
defaults: { _controller: myBundle:Security:login }
login_check:
path: /login_check
defaults: { _controller: myBundle:Security:loginCheck }
logout:
path: /logout
defaults: { _controller: myBundle:Security:logout }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/$, roles: IS_AUTHENTICATED_FULLY }
#This route for redirect to my home page when login succesfull
my_home_page:
#Path and controller here
The controllers for routes:
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
//If the user is login, go in, it's not necesary login again
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
return $this->redirectToRoute('my_home_page');
}
//If code here, logs are write in login and logut indiscriminate
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('myBundle:Security:login.html.twig',
array(
'last_username' => $lastUsername,
'error' => $error));
}
public function loginCheckAction(){
//I try code the file logs here for login succesfull
//But doesn't happen anything
}
public function logout(){
//I try code the file logs here for logout
//But doesn't happen anything
}
}
Finally in View
{% if error %}
<!-- This is for personalize the error menssage -->
<h3>User and/or pass incorrects</h3>
{% endif %}
<form action="{{ path('login_check') }}" method="post" autocomplete="OFF">
<label for="exampleInputEmail1">User</label>
<input type="text" name="_username" id="exampleInputEmail1">
<label for="exampleInputPassword1">Clave</label>
<input type="password" name="_password" id="exampleInputPassword1">
<input type="hidden" name="_target_path" value="my_home_page">
<button type="submit">Sing In</button>
</form>
I'm try get a solution specifing my own handles, but fails
(This solutions is for Symfony 2, looks deprecated)
You can see: https://gist.github.com/chalasr/69ad35c11e38b8cc717f
You want to use EventSubscribers, and specifically LogoutHandlerInterface and AuthenticationSuccessHandlerInterface like FOSUserBundle does.
This article goes into the specifics of how to use listeners with LogoutHandlerInterface (just ignore the part about using FOSUB as the user manager if you aren't using it).
the logout() and loginCheckAction() methods are not actually used, they exist to catch the requests for login and logout for the firewall.
About solution, efectly it's necessary implement (on symfony 3) EventSubscribers, in my case the Events: LogoutHandlerInterface and DefaultAuthenticationSuccessHandler. Just like #Spencer says.
Step for solution:
1) Creating namespace (one directory) Listeners on MyBundle/Listeners
2) Creating the Classes LogoutListener and LoginListener in Listener namespace.
The class LogoutListener implements the interface LogoutHandlerInterface and rewrite the method logout.
The class LoginListener extends from the class DefaultAuthenticationSuccessHandler. You could implement the interface AuthenticationSuccessHandlerInterface but... seems that you need write a method for management $http (determineTargetUrl()), onAuthenticationSuccess() must be return a redirect after the login sucessfull
Class LogoutListener
//Class for Logout Listener
<?php
namespace MyBundle\Listeners;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
class LogoutListener implements LogoutHandlerInterface {
public function __construct(){}
public function logout(Request $Request, Response $Response, TokenInterface $Token) {
//Code here that you want that happen when logut sucessfull.
//In my case, write logs files
}
}
?>
Class LoginListener
//Class LoginListener
<?php
namespace ReportesBundle\Listeners;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
class LoginListener extends DefaultAuthenticationSuccessHandler {
protected $httpUtils;
public function __construct(HttpUtils $httpUtils){
parent::__construct($httpUtils); }
public function onAuthenticationSuccess(Request $Request, TokenInterface $Token){
//Code here that you want that happen when login sucessfull.
//In my case, write logs files
return $this->httpUtils->createRedirectResponse($Request, $this->determineTargetUrl($Request));
}
}
?>
3) Declare services in services.yml
services:
#Declaring service for logout event
mybundle_logoutlistener:
class: MyBundle\Listeners\LogoutListener
#Declaring service for authentication success handler
security.authentication.success.handler:
class: MyBundle\Listeners\LoginListener
arguments: ["#security.http_utils"]
4) Configuring firewall in security.yml
firewalls:
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /login
check_path: /login_check
username_parameter: _username
password_parameter: _password
success_handler: security.authentication.success.handler
logout:
path: /logout
target: /login
handlers: [mybundle_logoutlistener]
Thanks for help!

How do I redirect to a dynamic custom route after register with FOSUserBundle

I've been looking for a while for this, but can't find the answer.
Expected behavior:
User goes on a product page
User wants to buy the product, so he needs to login / register (with a modal)
User doesn't have an account so he registers
After registration (there is no email confirmation), user is signed in and redirected to the current product page
User can proceed to checkout, etc...
This behavior is working in the case of a login, with the use of the _target_path parameter in the form.
However, this parameter does not apply in registration. This is kinda annoying, where did I miss something out ? I am looking into implementing a Listener on the registration success event but it seems really odd to not being as simple as for the login form.
Edit: found my solution, see my own answer below
Answering for those wondering too.
TL;DR : the registration process does not follow the login's
Thanks to #yceruto for the comment.
I made a quick listener on the REGISTRATION_SUCCESS event:
/**
* #param FormEvent $event
*/
public function redirectOnRegistration(FormEvent $event)
{
$route = $event->getRequest()->headers->get('referer', $this->router->generate('homepage'));
$response = new RedirectResponse($route);
$event->setResponse($response);
}
(I redirect on homepage if there is no referer).
In your security settings you must turn on referer:
firewalls:
dev:
pattern: ^/(_(profiler|wdt|console)|css|images|js)/
security: false
main:
pattern: ^/any_pattern
anonymous: ~
form_login:
login_path: any_pattern_login
check_path: any_pattern_login
use_referer: true
logout: true
access_denied_handler: app.security.access_denied_handler
guard:
authenticators:
- app.api_authenticator
provider: api_provider
logout:
path: any_pattern_logout
invalidate_session: true

Redirect from existent route to a new one

I'm trying to redirect if a user is not logged in (works with FOSUser and SonataAdmin) and calls either http://domain.com/app_dev.php (dev) or http://domain.com (prod) then I want in both cases redirect to /login so I wrote this config at app/routing.yml:
root:
path: /
defaults:
_controller: FrameworkBundle:Redirect:urlRedirect
path: /login
permanent: true
That works but I have a problem since I have this in a controller:
/**
* #param Request $request
*
* #return array
* #Route("/", name="brand")
* #Method("GET")
* #Template()
*/
public function indexAction(Request $request)
{
....
}
Then when I try to call brand route as per example using a link like: http://domain.com/app_dev.php/?email=7x-xRFClkjw (dev) or http://domain.com/?email=7x-xRFClkjw (prod) I got redirected to /login. My best idea is to change my code into this:
/**
* #param Request $request
*
* #return array
* #Route("/bp", name="brand")
* #Method("GET")
* #Template()
*/
public function indexAction(Request $request)
{
....
}
And then redirect using a route to the new /bp instead to / for that function (this is mainly because users already has a tons of emails that had links as the previously shared and I can't change that). But I've not idea in how to write this rule in routing.yml because if I wrote this:
bp:
path: /
defaults:
_controller: FrameworkBundle:Redirect:redirect
route: brand
permanent: true
I will end up with a redirect loop. I could change also how default route to /login is setup I just can't find the right documentation. The big idea behind this post is to setup default route for /. Can any give me some help on this?
As extra info this is part of my security.yml file:
firewalls:
...
admin_area:
pattern: ^/
anonymous: ~
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
# the user is redirected here when they need to log in
login_path: /login
# submit the login form here
check_path: /login_check
# login success redirecting options (read further below)
always_use_default_target_path: true
default_target_path: /admin/dashboard
target_path_parameter: _target_path
use_referer: false
failure_path: /admin/dashboard
failure_forward: false
logout:
path: /logout
UPDATE
Actually the code from #emmanuel-hdz-díaz give me another idea and I don't need to create a kernel listener or add to much code, simple by doing this at my controller:
if ($request->query->get('email')) {
...
} else {
return $this->redirect($this->generateUrl('sonata_user_admin_security_login'));
}
I was able to redirect the user to the /login route.
Throw a special exception, catch it in the kernel.request event, redirect from there.
You have to create a custom exception that you can store your route in.e.g:
class RouteException extends \Exception{
protected $route;
public function __construct($route){
$this->route = $route;
}
public function getRoute() { return $this->route; }
}
Now create a listener that will generate a RedirectResponse when it detects this exception, e.g:
class RedirectExceptionListener{
protected $router;
public function __construct(RouterInterface $router){
$this->router;
}
public function onException(GetResponseForExceptionEvent $event){
$e = $event->getException();
if($e instanceof RouteException){
$url = $this->router->generate($e->getRoute());
$event->setResponse(
new RedirectResponse($url);
);
}
}
}
# the service definition
your_listener:
class: RedirectExceptionListener
arguments:
- #router
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Note: you have to use the namespaces for the used classes.
This is basically what you can do to redirect from anywhere in your application, although it does seem like a smelly use of exceptions, so create a service Redirecter with a method redirect($route) that throws the exception for you, so the calling service will not know how the redirection is being done.The Redirecter can throw the exception or do anything required to redirect to the given route.
For more info on event listeners see Event Listeners.
Update: This is exactly how symfony redirects you to your login_path when you're not authenticated, see ExceptionListener
Not sure if I understand, but you want to restrict access to whole site? In that case just simply change you security setting, never add redirect to login, because it always redirect to login even if use is logged in. Here is simple configuration example:
security:
firewalls:
main:
pattern: ^/
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, role: ROLE_USER }
Try this:
In your controller inside yourAction():
//
$usr = $this->get('security.context')->getToken()->getUser();
if( $usr == 'anon.' )
{
return $this->redirect($this>generateUrl('sonata_user_admin_security_login'));
}
//

Symfony 2 after login do some extra job

In this case, after a successful login, I need to update the user's login time into the underlying table.
The User entity currently implements UserInterface and is doing fine. Just want to add some extra code to log the login date time of the user.
Searched the site and seemed to me to use an EventListener etc is a bit heavy. Other lighter alternatives?
You can implement a Success Hander.
Write a class that implement the AuthenticationSuccessHandlerInterface:
Define it as a service (you can inject other services like doctrine
or the security context, in your case the Session)
Add the handler service in the security config like:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
success_handler: some.service.id
logout:
path: logout
target: /
Check this for a complete example and the reference doc for all the symfony2 security configuration options (you can configure the failure_handler also).
In my working solutions I implements the method onSecurityInteractiveLogin in my listener as:
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$user->setLastLogin(new \Datetime());
// Entity manager injected by container in the service definition
$this->em->persist($user);
$this->em->flush();
}
Hope this help

Symfony2 Firewall: Redirect to registration form instead of login

I am working on a simple shop (to compile offers) based on Symfony2.
After adding items to his cart, a user can proceed to a summary of his offer and then request the compiled offer.
The summary page is protected by the following firewall:
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
provider: default
form_login:
login_path: acme_security_login_route
check_path: acme_security_login_check_route
csrf_provider: form.csrf_provider
logout: ~
default:
anonymous: ~
access_control:
- { path: ^/request-offer, roles: ROLE_CLIENT }
providers:
default:
entity: { class: AcmeShopBundle:User }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
Acme\ShopBundle\Entity\User:
algorithm: bcrypt
cost: 15
This means that if the client is logged in, he will directly get to the summary, and if not, he is redirected to the login page.
Now as it is more probable for the client to be a new customer I would like to redirect to the registration form, instead.
The options described in the SecurityBundle Configuration Reference don't allow this.
Of course changing the login_path is also not a solution.
What would be the nicest possible solution for that?
In my opinion a nice solution is to add an own AccessDeniedExceptionHandler, how to do this is explained here.
Using Symfony2's AccessDeniedHandlerInterface
Further more, you could made the service configurable via the Configuration Component, so that you pass as argument the route to redirect.
http://symfony.com/doc/current/components/config/definition.html
If you do this you can change, if you got more users, to redirect back to login page without editing any class.
Nextar's answer lead me to the solution.
Quoting this question:
the service pointed by access_denied_handler is only called if the user has unsufficient privilege to access the resource. If the user is not authenticated at all access_dened_handler is never called. Providing a service to entry_point in security.yml did actually solve the problem
So I ended up with this:
#services.yml
acme.security.entry_point.class: ArtCube\ShopBundle\Service\EntryPointHandler
services:
acme.security.entry_point_handler:
class: %acme.security.entry_point.class%
arguments:
router: #router
Then I added this service to my security.yml, right after logout: ~ line (see initial question):
entry_point: art_cube_shop.security.entry_point_handler
And created the service:
// Acme/ShopBundle/Service/EntryPointHandler.php
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
class EntryPointHandler implements AuthenticationEntryPointInterface {
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function start(Request $request, AuthenticationException $authException = null)
{
if($authException instanceof InsufficientAuthenticationException)
{
return new RedirectResponse($this->router->generate('acme_security_registration_route'));
}
else
{
return new RedirectResponse($this->router->generate('acme_security_login_route'));
}
}
}

Categories