Symfony 6.0 - Force logout user in controller - symfony

How can i force logout user logged from controle on the new Symfony 6 ? (Version 6.0.1)
I tried $tokenStorage->setToken($token); but setToken() need 2 args:
(public function setToken(string $tokenId, string $token);)
I tried $request->getSession()->invalidate(); but my user is always logged...
I want to logout the user and redirect to another route (à don't want redirect to logout route)
Thank you
I can't use /logout because i'm in a controller, and sometime I have to make sure no user is logged, because i do treatment when I'm comming to this route.
I need this:
When i go to /validate route:
if user : logged => logout
change somethings to my user, other user and flush some logs to bdd
redirect to login page to force login back the user
My service:
<?php
namespace App\Service;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class SecurityService
{
public function forceLogout(
Request $request,
EventDispatcherInterface $eventDispatcher,
TokenStorageInterface $tokenStorage) : void
{
$logoutEvent = new LogoutEvent($request, $tokenStorage->getToken());
$eventDispatcher->dispatch($logoutEvent);
$tokenStorage->setToken(null);
}
}
This don't work, my $eventDispatcher->dispacth($logoutEvent) work only before i refresh my page, after i'm logged again !

I found soluce :
public function forceLogout() : void
{
$logoutEvent = new LogoutEvent($this->requestStack->getCurrentRequest(), $this->tokenStorage->getToken());
$this->eventDispatcher->dispatch($logoutEvent);
$this->tokenStorage->setToken(null);
$response = new Response();
$response->headers->clearCookie('REMEMBERME');
$response->send();
}

just redirect to the logout route:
return $this->redirect($this->generateUrl('YourLogoutRouteName'));

Related

Call the wordpress logout interface with a cookie, but the front end is not logged out

I tried to use RestTemplate to carry cookies and request a custom Wordpress logout interface, but the front end did not log out. When I directly accessed the logout url in the browser, I was able to logout successfully.
Here is my RestTemplate code:
public void logoutOfWordpress(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
String url = wordpressProper.getRestUrl() + "/community-rest/logout";
HttpHeaders httpHeaders = buildHeaderByCookies(cookies);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(httpHeaders), String.class);
Object body = exchange.getBody();
}
This is the WordPress logout interface:
add_action( 'rest_api_init', 'community_rest_logout');
function community_rest_logout() {
register_rest_route(
'community-rest',
'/logout/',
array(
'methods' => 'GET',
'callback' => 'logout'
)
);
}
function logout() {
wp_logout();
wp_redirect('http://192.168.100.5:8888/sign_in');
exit;
}
I tried to use Postman to call this logout interface and found that there is
Set-Cookie in the response header. It seems that this is necessary for logout. It seems that I can directly set the Max-Age of the cookie to
0 to achieve the purpose of logout without having to to call the logout interface
.
What are you using for user login? Sessions are not started internally in wordpress rest api. You should be able to get the user_id if the user is logged in on your logout endpoint. Can you get this?
Generally, nonce is used on rest for unregistered users. You need to send the nonce value in header or parameter. For registered users, you need to authenticate cookies or you can use some rest api authentication plugins.
For a simple hack you can use this at the top of your endpoints:
public function init_cookie() {
$userID = wp_validate_logged_in_cookie(false);
if($userID) {
wp_set_current_user($userID);
}
}
I would also suggest defining your code in a class or prefixing the names of the functions. The logout function is a very generic name. If this is just for an example, ignore what I said.

Symfony 1.4 - Login to frontend(as client) from backend

I'm using SfGuardPlugin and on the backend of my site I have the full list of users and I want to be able to login on the frontend with the user that I choose from the list.
I've tried this method:
public function executeListLogin(sfWebRequest $request) {
// client that I've selected from list
$client = $this->getRoute()->getObject();
// create instance if doesn't exist
if(!sfContext::hasInstance('frontend')){
sfContext::createInstance(ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false));
}
// switch to frontend
sfContext::switchTo('frontend');
// also tried with: sfContext::getInstance('frontend')
sfContext::getInstance()->getUser()->signin($client->getSfGuardUser(), true);
// redirect to frontend homepage
$this->redirect('#homepage');
}
It redirects me to the frontend homepage, but I'm not logged in.
After more digging I've found out that I'm logged out from backend and now I'm logged in on frontend with admin instead of the user I choose. So the sfContext::switchTo doesn't work correctly.
I've found a workaround.
Create a new column(login_frontend_key) on the users table.
Create a new frontend route with a required parameter(named key)
Create the action in frontend controller witch is responsible for sign in the user, ex:
public function executeLoginClientKey(sfWebRequest $request){
// get the client by key
$this->forward404Unless($user = ClientPeer::getByLoginFrontendKey($request->getParameter("key")));
// if you already logged in with another user, signout
if($this->getUser()->isAuthenticated())
{
$this->getUser()->signOut();
}
// signin with the new user
$this->getUser()->signIn($user->getsfGuardUser(), false);
$user->setLoginFrontendKey(null); // delete the key from DB
$user->save();
$this->redirect('#homepage');
}
Use this to generate cross application links http://symfony.com/blog/cross-application-links
Create the object action on the backend users page:
public function executeListLogin(sfWebRequest $request)
{
// get the selected client
$client = $this->getRoute()->getObject();
$key = $client->generateLoginFrontendKey(); // generate a random key
$client->setLoginFrontendKey($key); // store the key in DB
$client->save();
// generate the frontend url for login
$url = $this->getContext()->getConfiguration()->generateFrontendUrl('login_frontend_key', array('key' => $key, 'sf_culture' => 'nb'));
$this->redirect($url);
}

fosuserbundle ldap configuration for strange use case

I'm trying to create a fosuserbundle for a quite strange use case, which is mandatory requirement, so no space to diplomacy.
Use case is as follow:
users in a mongo db table populated by jms messages -no registration form
users log in by ldap
user record not created by ldap, after a successful login username is checked against mongodb document
Considering that ldap could successfully log in people that exhist in ldap but cannot access site (but login is still successful), what could be the best way to perform such authentication chain?
I was thinking about some possible options:
listen on interactive login event, but imho there's no way to modify an onSuccess event
create a custom AuthenticationListener to do another check inside onSuccess method
chain authentication using scheb two-factor bundle
any hint?
I've used Fr3DLdapBundle which can be incorporate with FOSUserBundle quite easily (I'm using the 2.0.x version, I have no idea if the previous ones will do the same or be as easy to set up).
In the LdapManager (by default) it creates a new user if one is not already on the database which is not what I wanted (and doesn't seem to be what you want) so I have added my own manager that checks for the presence of the user in the database and then deals with the accordingly.
use FR3D\LdapBundle\Ldap\LdapManager as BaseLdapManager;
.. Other use stuff ..
class LdapManager extends BaseLdapManager
{
protected $userRepository;
protected $usernameCanonicalizer;
public function __construct(
LdapDriverInterface $driver,
$userManager,
array $params,
ObjectRepository $userRepository,
CanonicalizerInterface $usernameCanonicalizer
) {
parent::__construct($driver, $userManager, $params);
$this->userRepository = $userRepository;
$this->usernameCanonicalizer = $usernameCanonicalizer;
}
/**
* {#inheritDoc}
*/
public function findUserBy(array $criteria)
{
$filter = $this->buildFilter($criteria);
$entries = $this->driver->search(
$this->params['baseDn'], $filter, $this->ldapAttributes
);
if ($entries['count'] > 1) {
throw new \Exception('This search can only return a single user');
}
if ($entries['count'] == 0) {
return false;
}
$uid = $entries[0]['uid'][0];
$usernameCanonical = $this->usernameCanonicalizer->canonicalize($uid);
$user = $this->userRepository->findOneBy(
array('usernameCanonical' => $usernameCanonical)
);
if (null === $user) {
throw new \Exception('Your account has yet to be set up. See Admin.');
}
return $user;
}

How to set remember me cookie when logging user inside Controller in Symfony2

I have made my own custom FB login function because I need a certain flow that I couldn't achieve with HWIOAuthBundle. Everything works great, except one thing... I don't know how to set remember me functionality when I log in a user through my controller.
This is the LOGIN code I've got so far:
public function loginAction() {
// Facebook login code here
...
// User already connected with FB acc. Just log in.
$token = new UsernamePasswordToken($user, null, "main", $user->getRoles());
$this->get("security.context")->setToken($token);
//now dispatch the login event
$request = $this->get("request");
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
// Redirect user to hime page
return $this->redirect($this->generateUrl('/home'));
}
Thank you for any help or advice in advance!
You can use this remember token not the normal one it should be fixed :
$key = $this->container->getParameter('secret');
$token = new RememberMeToken($user, 'main', $key); //main is your firewall use name you gave to it.
$this->get('security.context')->setToken($token);

Symfony2 - Redirect response from request EventListener in dev mode while ignoring built in request events

I am building my own user management system in Symfony2 (not using FOSUserBundle) and want to be able to force users to change their password.
I have setup an EventListener to listen to the kernal.request event, then I perform some logic in the listener to determine if the user needs to change their password; if they do, then they are redirected to a "Change Password" route.
I add the service to my config.yml to listen on the kernal.request:
password_change_listener:
class: Acme\AdminBundle\EventListener\PasswordChangeListener
arguments: [ #service_container ]
tags:
- { name: kernel.event_listener, event: kernel.request, method: onMustChangepasswordEvent }
And then the listener:
public function onMustChangepasswordEvent(GetResponseEvent $event) {
$securityContext = $this->container->get('security.context');
// if not logged in, no need to change password
if ( !$securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') )
return;
// If already on the change_password page, no need to change password
$changePasswordRoute = 'change_password';
$_route = $event->getRequest()->get('_route');
if ($changePasswordRoute == $_route)
return;
// Check the user object to see if user needs to change password
$user = $this->getUser();
if (!$user->getMustChangePassword())
return;
// If still here, redirect to the change password page
$url = $this->container->get('router')->generate($changePasswordRoute);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
The problem I am having is that in dev mode, my listener is also redirecting the profiler bar and assetic request events. It works when I dump assets and clear cache and view the site in production mode.
Is there a way I can ignore the events from assetic/profiler bar/any other internal controllers? Or a better way to redirect a user to the change_password page (not only on login success)?
Going crazy thinking up wild hack solutions, but surely there is a way to handle this elegantly in Symfony2?
This is the very hack solution I am using for now:
Determine if in dev environment
If so, get an array of all the routes
Filter the route array so that only the routes I have added remain
Compare the current route to the array of routes
If a match is found, this means that the event is not an in-built controller, but must be one that I have added, so perform the redirect.
And this is the madness that makes that work:
// determine if in dev environment
if (($this->container->getParameter('kernel.environment') == 'dev'))
{
// Get array of all routes that are not built in
// (i.e You have added them yourself in a routing.yml file).
// Then get the current route, and check if it exists in the array
$myAppName = 'Acme';
$routes = $this->getAllNonInternalRoutes($myAppName);
$currentRoute = $event->getRequest()->get('_route');
if(!in_array($currentRoute, $routes))
return;
}
// If still here, success, you have ignored the assetic and
// web profiler actions, and any other actions that you did not add
// yourself in a routing.yml file! Go ahead and redirect!
$url = $this->container->get('router')->generate('change_password_route');
$response = new RedirectResponse($url);
$event->setResponse($response);
And the crazy hack function getAllNonInternalRoutes() that makes it work (which is a modification of code I found here by Qoop:
private function getAllNonInternalRoutes($app_name) {
$router = $this->container->get('router');
$collection = $router->getRouteCollection();
$allRoutes = $collection->all();
$routes = array();
foreach ($allRoutes as $route => $params)
{
$defaults = $params->getDefaults();
if (isset($defaults['_controller']))
{
$controllerAction = explode(':', $defaults['_controller']);
$controller = $controllerAction[0];
if ((strpos($controller, $app_name) === 0))
$routes[]= $route;
}
}
return $routes;
}

Resources