Symfony2 firewall and FOSRestBundle - symfony

For my web service using FOSRestBundle, I created a firewall that forces a login to access the application.
My problem is that when I make a call to the API via ajax, I need to get the error code 401 when the user is not authenticated rather than receive the html source code of the login form. How do I configure the application?
secured_area:
pattern: ^/
form_login:
provider: fos_userbundle
use_forward: false
default_target_path: /w
logout:
path: /logout
target: /login
EDIT:
Thanks to Ryan here is the KernelExceptionListener method.
public function onKernelException( GetResponseForExceptionEvent $event ) {
// get exception
$exception = $event->getException();
// get path
$path = $event->getRequest()->getPathInfo();
if ( $exception instanceOf AuthenticationException && ($event->getRequest()->isXmlHttpRequest() || strpos( $path, '/api' ) === 0) ) {
$response = new Response();
$response->setStatusCode( 401 );
$event->setResponse( $response );
$event->stopPropagation();
}
}

You used the word authentication and not authorization and unfortunately this does not seem to be already written. Therefore, you will probably need to create your own.
In the generic case, a simple Kernel Event listener to intercept the AuthenticationException exception should be created. Capturing this event should allow you to perform any action you like before the redirect to the login page.
The FOSRestBundle should provide a good example of how to do this. The FOSRestBundle provides this functionality for the authorization layer currently (AccessDeniedException). With a little bit of modification the same framework should provide the capability to do the same for the authentication layer as well.
See pull #308 for the change set that provides the authorization listeners.
See Security Exception Listener for documentation on how to configure the listeners.

Related

Login and set remember_me cookie after user registration in Symfony

I have a controller action which processes user's data, registers the user, logs them in and redirects them to another controller's action. However I am unable to get the "remember_me" cookie set.
On a successful registration, the controller will obtain the $user object and then pass this onto the authenticateUserAndHandleSuccess() method of the GuardAuthenticatorHandler which is part Symfony's Security Bundle.
use Symfony\Component\HttpFoundation\Request;
use App\Security\LoginFormAuthenticator;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
public function register(Request $request, LoginFormAuthenticator $authenticator, GuardAuthenticatorHandler $guardHandler) {
// do something... and get $user object
return $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$authenticator,
'main'
);
}
What this does is to automatically login the user. However, it does not set the remember_me cookie which is presently working and used in the login form.
From the security.yaml
remember_me:
secret: '%kernel.secret%'
lifetime: 2592000 # 1 month in seconds
path: /
remember_me_parameter: login_form[_remember_me]
How could the controller be changed to also set the "remember_me" cookie?
I don't know if this is a good idea to set remember_me during registration but if you really need it just check onLoginSuccess method of TokenBasedRememberMeServices which is executed during login process when proper remember me parameter is being send - it's just about setting proper cookie.

Symfony2 phpunit functional test custom user authentication fails after redirect (session related)

Intro: Custom user implementation to be able to use and Wordpress users:
In our project, we have implemented a custom user provider (for Wordpress users - implements UserProviderInterface) with corresponding custom user (WordpressUser implements UserInterface, EquatableInterface). I have setup a firewall in the security.yml and implemented several voters.
# app/config/security.yml
security:
providers:
wordpress:
id: my_wordpress_user_provider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
default:
anonymous: ~
http_basic: ~
form_login:
login_path: /account
Functional phpunit testing:
So far so good - but now the tricky part: mocking authenticated (Wordpress) users in functional phpunit tests. I have succeeded mocking the WordpressUserProvider so a mocked WordpressUser will be returned on loadUserByUsername(..). In our BaseTestCase (extends WebTestCase) the mocked WordpressUser gets authenticated and the token is stored to session.
//in: class BaseTestCase extends WebTestCase
/**
* Login Wordpress user
* #param WordpressUser $wpUser
*/
private function _logIn(WordpressUser $wpUser)
{
$session = self::get('session');
$firewall = 'default';
$token = new UsernamePasswordToken($wpUser, $wpUser->getPassword(), $firewall, $wpUser->getRoles());
$session->set('_security_' . $firewall, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
self::$_client->getCookieJar()->set($cookie);
}
The problem: losing session data on new request:
The simple tests succeed on the authentication part. Until tests with a redirect. The user is only authenticated one request, and 'forgotten' after a redirect. This is because the Symfony2 test client will shutdown() and boot() the kernel on each request, and in this way, the session gets lost.
Workarounds/solutions:
In a solution provided in question 12680675 only user ID should be used for the UsernamePasswordToken(..) to solve this. Our project needs the full user object.
In the solution provided in Unable to simulate HTTP authentication in functional test the basic HTTP authentication is used. In this case the full user object - including roles - cannot be used.
As suggested by Isolation of tests in Symfony2 you can persist instances by overriding the doRequest() method in the test client. As suggested I have created a custom test client and made an override on the doRequest() method.
Custom test client to 'store' session data between requests:
namespace NS\MyBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Client as BaseClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Class Client
* Overrides and extends the default Test Client
* #package NS\MyBundle\Tests
*/
class Client extends BaseClient
{
static protected $session;
protected $requested = false;
/**
* {#inheritdoc}
*
* #param Request $request A Request instance
*
* #return Response A Response instance
*/
protected function doRequest($request)
{
if ($this->requested) {
$this->kernel->shutdown();
$this->kernel->boot();
}
$this->injectSession();
$this->requested = true;
return $this->kernel->handle($request);
}
/**
* Inject existing session for request
*/
protected function injectSession()
{
if (null === self::$session) {
self::$session = $this->getContainer()->get('session');
} else {
$this->getContainer()->set('session', self::$session);
}
}
}
Without the if statement holding the shutdown() and boot() calls, this method is working more or less. There are some weird problems where $_SERVER index keys cannot be found so I would like to properly re-instantiate the kernel container for other aspects of the system. While keeping the if statement, users cannot be authenticated, though the session data is the same before and during/after the request (checked by var_export to log).
Question(s):
What am I missing in this this approach that causes the authentication to fail? Is the authentication (and session check) done directly on/after kernel boot() or am I missing something else? Does anyone has another/better solution to keep the session intact so users will be authenticated in functional tests? Thank you in advance for your answer.
--EDIT--
In addition: the session storage for the test environment is set to session.storage.mock_file. In this way, the session should already be persisted between requests as describe by Symfony2 components here. When checked in the test after a (second) request, the session seems to be intact (but somehow ignored by the authentication layer?).
# app/config/config_test.yml
# ..
framework:
test: ~
session:
storage_id: session.storage.mock_file
profiler:
collect: false
web_profiler:
toolbar: false
intercept_redirects: false
# ..
My assumptions were close; it was not the session that was not persisted, the problem was in the case that mocked services are 'erased' by the kernel at a fresh request. This is the basic behaviour of functional phpunit testing...
I found out that this had to be the problem while debugging in the Symfony\Component\Security\Http\Firewall\AccessListener. There the token was found, and the (not anymore) mocked custom WordpressUser was there - empty. This explains why setting the username only instead of user object worked in the suggested workarounds stated above (no need of the mocked User class).
Solution
First of all, you don't need to override the Client as suggested in my question above. To be able to persist your mocked classes, you will have to extend the AppKernel and make some sort of kernel-modifier override with a closure as parameter. There is an explanation here on LyRiXx Blog. After injecting with a closure, you could restore the service mock after a request.
// /app/AppTestKernel.php
/**
* Extend the kernel so a service mock can be restored into the container
* after a request.
*/
require_once __DIR__.'/AppKernel.php';
class AppTestKernel extends AppKernel
{
private $kernelModifier = null;
public function boot()
{
parent::boot();
if ($kernelModifier = $this->kernelModifier) {
$kernelModifier($this);
};
}
/**
* Inject with closure
* Next request will restore the injected services
*
* #param callable $kernelModifier
*/
public function setKernelModifier(\Closure $kernelModifier)
{
$this->kernelModifier = $kernelModifier;
}
}
Usage (in your functional test):
$mock = $this->getMockBuilder(..);
..
static::$kernel->setKernelModifier(function($kernel) use ($mock) {
$kernel->getContainer()->set('bundle_service_name', $mock);
});
I still have to tweak the class and extended WebTestCase class, but this seems to work for me. I hope I can point someone else in the right(?) direction with this answer.

Symfony 2: how to have the firewall custom redirect upon custom event

my symfony app deals with multiple "blogs" (multisite wordpress style)
I have some special status for my blogs among which : closed_status.
I created a Voter for some special rights and that allows me to know if the voter should allow access or not upon verification that the blog we're accessing is open/closed...
** example url : http://blog01.myapp.com http://blog18.myapp.com ....
For now i throw a 403 error and use custom management of 403 exception but i'd like to avoid that and rather redirect at the momment of the voter processing my request.
** like what happens when you're not authenticated and you're being redirected to /login or such.
I'm not asking for a full solution, could you just tell me what classes/concepts i should be looking into ?
I thought or listeners+entryPoints thingy but don't really get how it works.
It seems to me that this needAuthentication ==> redirect to login thing is very spécifically coded in symfony.
ps: I'm using fosUserBundle and tried to override as little as i could withing the bundle and concerning hte firewall too.
The flow i'd like to achieve:
request to a blog url
=> myCustomVoter denies and dispatch(CLOSED) or dispatch(PRIVATE) or dispatch(VIPONLY)
=> and it results in a clean redirection to my setpsController actions (defined somewhere)
Dispatch a custom event in a voter. In the event listener for that event set response to RedirectResponse
Ok,
I noticed (think) that i can't add a listener to the current firewall (tell me if I'm wrong).
I also noticed that to do what i want i need to setResponse to a GetResponseEvent.
So I just created a generic listener, not attached to the firewall:
It listens to my custom event and the kernel.request that is a GetResponseEvent
For now I'm just hopping all will be listened in the order that suits me:
my service determinig if the blog is open, public... and that will eventually dispatch my custom event
my custom listener that will receive the custom event and prepare a response
my custom listener that receive the kernel.request and inject my calculated response if it exists
For my first tests it seems to work, but i need to test extensively to be sure that the firewall listeners don't mess with my process (it should not i think)
Here is how i defined my listener.
route_access_listener:
class: [...]\RouteAccessListener
arguments:
- #service_container
tags:
- { name: kernel.event_listener, event: routeAccessRedirect, method: onCustomRouteAccess }
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Here is the implementation of my listener:
[...]
public function onKernelRequest(GetResponseEvent $event)
{
if($this->response)
$event->setResponse($this->response);
}
public function onCustomRouteAccess(RouteAccessEvent $event)
{
$type = $event->getType();
switch($type){
case RouteAccessManager::DR_IS_USER_OF_OTHER_BLOG:
$redirectPath = 'link_to_blog';
break;
case RouteAccessManager::DR_BLOG_CLOSED:
$redirectPath = 'info_closed_blog';
break;
case RouteAccessManager::DR_PRIVATE_BLOG:
$redirectPath = 'info_private_blog';
break;
case RouteAccessManager::DR_USER_NOT_ENABLED_BLOG:
$redirectPath = 'info_not_enabled_on_blog';
break;
case RouteAccessManager::DR_INVALID_HOSTNAME:
$redirectPath = 'info_invalid_hostname';
break;
}
$url = $this->container->get('router')->generate($redirectPath);
$this->response = new RedirectResponse($url);
}

How to implement FosOAuthServerBundle to secure a REST API?

I would like to provide a RESTful API secured with OAuth2 using FOSOAuthServerBundle and I'm not really sure about what I have to do.
I followed basic steps from the documentation but some things are missing and I can't find a complete example of what I need.
So, I tried to understand the best I could this example of implementation (the only one I found) but there are still things I don't understand.
First, why do we need a login page in an API? Let's suppose my client is a iPhone or Android App, I see the interest of the login page on the app, but I think the client have just to call a webservice from the API to get its token, am I wrong? So how to implement autorisation and token providing via REST endpoint?
Then, documentation tells to write this firewall :
oauth_authorize:
pattern: ^/oauth/v2/auth
# Add your favorite authentication process here
And I don't know how to add an authentication process. Should I write my own one, for example following this tutorial or am I completely wrong?
Globally, can someone take the time to explain the process needed, after the five steps in the docs, to provide an OAuth2 secured RESTful API? It would be very nice...
EDIT after #Sehael answer :
I still have some questions before it is perfect...
What is represented by the "Client" here? For exemaple, should I create a client for a iPhone app, and another for an Android app? and do I have to create a new client for every instance wanting to use the API? what is the best practice in this case?
Unlike you, I don't use the OAuth process for the front website but the "classic" symfony way. Does it seem strange to you, or is it normal?
What is the usefulness of the refresh_token? How to use it?
I tried to test my new OAuth protected services. I used POSTman chrome extension, which support OAuth 1.0, does OAuth2 look like OAuth1 enough to be tested with POSTman? There is a "secret token" field that I don't know how to fill. If I can't, I'd be glad to see your (#Sehael) PHP class, as you proposed. / Edit : OK I think I found the answer for this one. I just added access_token as GET parameter with the token as value. It seems to be working. It is unfortunate that I have to do reverse engenering on bundle code to find that instead of read it in documentation.
Anyway, thanks a lot !
I have also found that the documentation can be a bit confusing. But after many hours of trying, I figured it out with the help of this blog (update - the blog no longer exists, changed to Internet Archive). In your case, you don't need a firewall entry for ^/oauth/v2/auth because this is for the authorization page. You need to remember what oAuth is able to do... it is used for more than just a REST api. But if a REST api is what you want to protect, you don't need it. here is an example firewall config from my app:
firewalls:
oauth_token:
pattern: ^/oauth/v2/token
security: false
api_firewall:
pattern: ^/api/.*
fos_oauth: true
stateless: true
anonymous: false
secure_area:
pattern: ^/
fos_oauth: true
form_login:
provider: user_provider
check_path: /oauth/v2/auth_login_check
login_path: /oauth/v2/auth_login
logout:
path: /logout
target: /
anonymous: ~
access_control:
- { path: ^/oauth/v2/auth_login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
Notice that you need to define a user provider. If you use FOSUserBundle, there is a user provider already created for you. In my case, I made my own and created a service out of it.
And in my config.yml:
fos_oauth_server:
db_driver: orm
client_class: BB\AuthBundle\Entity\Client
access_token_class: BB\AuthBundle\Entity\AccessToken
refresh_token_class: BB\AuthBundle\Entity\RefreshToken
auth_code_class: BB\AuthBundle\Entity\AuthCode
service:
user_provider: platform.user.provider
options:
supported_scopes: user
I should also mention that the tables that you create in the database (access_token, client, auth_code, refresh_token) are required to have more fields than what is shown in the docs...
Access Token Table: id(int), client_id(int), user_id(int), token(string), scope(string), expires_at(int)
Client Table: id(int), random_id(string), secret(string), redirect_urls(string), allowed_grant_types(string)
Auth Code Table: id(int), client_id(int), user_id(int)
Refresh Token Table: id(int), client_id(int), user_id(int), token(string), expires_at(int), scope(string)
These tables will store the info needed for oAuth, so update your Doctrine entities so they match the db tables like above.
And then you need a way to actually generate the secret and client_id, so that's where the "Creating a Client" section of the docs comes in, although it isn't very helpful...
Create a file at /src/My/AuthBundle/Command/CreateClientCommand.php (you will need to create the folder Command) This code is from the article I linked to above, and shows an example of what you can put into this file:
<?php
# src/Acme/DemoBundle/Command/CreateClientCommand.php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CreateClientCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('acme:oauth-server:client:create')
->setDescription('Creates a new client')
->addOption(
'redirect-uri',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.',
null
)
->addOption(
'grant-type',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Sets allowed grant type for client. Use this option multiple times to set multiple grant types..',
null
)
->setHelp(
<<<EOT
The <info>%command.name%</info>command creates a new client.
<info>php %command.full_name% [--redirect-uri=...] [--grant-type=...] name</info>
EOT
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$clientManager = $this->getContainer()->get('fos_oauth_server.client_manager.default');
$client = $clientManager->createClient();
$client->setRedirectUris($input->getOption('redirect-uri'));
$client->setAllowedGrantTypes($input->getOption('grant-type'));
$clientManager->updateClient($client);
$output->writeln(
sprintf(
'Added a new client with public id <info>%s</info>, secret <info>%s</info>',
$client->getPublicId(),
$client->getSecret()
)
);
}
}
Then to actually create the client_id and secret, execute this command from the command line (this will insert into the database the necessary ids and stuff):
php app/console acme:oauth-server:client:create --redirect-uri="http://clinet.local/" --grant-type="password" --grant-type="refresh_token" --grant-type="client_credentials"
notice that the acme:oauth-server:client:create can be whatever you actually name your command in the CreateClientCommand.php file with $this->setName('acme:oauth-server:client:create').
Once you have the client_id and secret, you are ready to authenticate. Make a request in your browser that is something like this:
http://example.com/oauth/v2/token?client_id=[CLIENT_ID_YOU GENERATED]&client_secret=[SECRET_YOU_GENERATED]&grant_type=password&username=[USERNAME]&password=[PASSWORD]
Hopefully it works for you. There is definitly alot to configure, just try to take it one step at a time.
I also wrote a simple PHP class to call my Symfony REST api using oAuth, if you think that would be useful, let me know and I can pass it on.
UPDATE
In response to your further questions:
The "client" is described on the same blog, just a different article. Read the Clients and Scopes section here, it should clarify for you what a client is. Like mentioned in the article, you don't need a client for every user. You could have a single client for all your users if you want.
I actually am also using the classic Symfony authentication for my frontend site, but that may change in the future. So it's always good to keep these things in the back of your mind, but I wouldn't say that it is strange to combine the two methods.
The refresh_token is used when the access_token has expired and you want to request a new access_token without resending the user credentials. instead, you send the refresh token and get a new access_token. This isn't really necessary for a REST API because a single request probably won't take long enough for the access_token to expire.
oAuth1 and oAuth2 are very different, so I would assume that the method you use wouldn't work, but I've never tried with that. But just for testing, you can make a normal GET or POST request, as long as you pass the access_token=[ACCESS_TOKEN] in the GET query string (for all types of requests, actually).
But anyways, here is my class. I used a config file to store some variables, and I didn't implement the ability to DELETE, but that isn't too hard.
class RestRequest{
private $token_url;
private $access_token;
private $refresh_token;
private $client_id;
private $client_secret;
public function __construct(){
include 'config.php';
$this->client_id = $conf['client_id'];
$this->client_secret = $conf['client_secret'];
$this->token_url = $conf['token_url'];
$params = array(
'client_id'=>$this->client_id,
'client_secret'=>$this->client_secret,
'username'=>$conf['rest_user'],
'password'=>$conf['rest_pass'],
'grant_type'=>'password'
);
$result = $this->call($this->token_url, 'GET', $params);
$this->access_token = $result->access_token;
$this->refresh_token = $result->refresh_token;
}
public function getToken(){
return $this->access_token;
}
public function refreshToken(){
$params = array(
'client_id'=>$this->client_id,
'client_secret'=>$this->client_secret,
'refresh_token'=>$this->refresh_token,
'grant_type'=>'refresh_token'
);
$result = $this->call($this->token_url, "GET", $params);
$this->access_token = $result->access_token;
$this->refresh_token = $result->refresh_token;
return $this->access_token;
}
public function call($url, $method, $getParams = array(), $postParams = array()){
ob_start();
$curl_request = curl_init();
curl_setopt($curl_request, CURLOPT_HEADER, 0); // don't include the header info in the output
curl_setopt($curl_request, CURLOPT_RETURNTRANSFER, 1); // don't display the output on the screen
$url = $url."?".http_build_query($getParams);
switch(strtoupper($method)){
case "POST": // Set the request options for POST requests (create)
curl_setopt($curl_request, CURLOPT_URL, $url); // request URL
curl_setopt($curl_request, CURLOPT_POST, 1); // set request type to POST
curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params
break;
case "GET": // Set the request options for GET requests (read)
curl_setopt($curl_request, CURLOPT_URL, $url); // request URL and params
break;
case "PUT": // Set the request options for PUT requests (update)
curl_setopt($curl_request, CURLOPT_URL, $url); // request URL
curl_setopt($curl_request, CURLOPT_CUSTOMREQUEST, "PUT"); // set request type
curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params
break;
case "DELETE":
break;
default:
curl_setopt($curl_request, CURLOPT_URL, $url);
break;
}
$result = curl_exec($curl_request); // execute the request
if($result === false){
$result = curl_error($curl_request);
}
curl_close($curl_request);
ob_end_flush();
return json_decode($result);
}
}
And then to use the class, just:
$request = new RestRequest();
$insertUrl = "http://example.com/api/users";
$postParams = array(
"username"=>"test",
"is_active"=>'false',
"other"=>"3g12g53g5gg4g246542g542g4"
);
$getParams = array("access_token"=>$request->getToken());
$response = $request->call($insertUrl, "POST", $getParams, $postParams);

Symfony2: how to log user out manually in controller?

i would like to do something like that in controller to log user out:
$user = $this->get('security.context')->getToken()->getUser();
$user->logOut();
Logout in Symfony2 is handled by so called logout handler which is just a lister that is executed when URL match pattern from security configuration, ie. if URL is let's say /logout then this listener is executed. There are two build-in logout handlers:
CookieClearingLogoutHandler which simply clears all cookies.
SessionLogoutHandler which invalidates the session
All you have to do is the very same the last one does. You can achieve it by simply calling:
Legacy Symfony
$this->get('security.context')->setToken(null);
$this->get('request')->getSession()->invalidate();
Symfony 2.6
$this->get('security.token_storage')->setToken(null);
$this->get('request')->getSession()->invalidate();
Warning
This will only work when remember me functionality is disabled. In other case, user will be logged in back again by means of a remember me cookie with the next request.
Please consider the extended solution if you are using remember me functionality: https://stackoverflow.com/a/28828377/1056679
Invalidating the user's session might cause some unwanted results. Symfony's firewall has a listener that always checks and refreshes the user's token. You could just do a redirect to the default logout route that you have specified in your firewall.yml (or security.yaml)
In Controller you can do this:
$this->redirect($this->generateUrl('your_logout_url'));
If you don't know the name of the logout route (your_logout_url), you can get it from the Symfony console by using this command:
app/console router:match /logout
Or newer Symfony versions:
bin/console router:match /logout
:)
We have to set user as an anonymous user when logging out. Then we can use
$token->getUser()->getRoles(); in controller or {% if is_granted('ROLE_USER') %} in the twig template.
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
...
//$providerKey = $this->container->getParameter('fos_user.firewall_name');
$token = new AnonymousToken($providerKey, 'anon.');
$this->get('security.context')->setToken($token);
$this->get('request')->getSession()->invalidate();
If rememberme functionality is enabled for your site you should also clean rememberme cookie:
$this->get('security.context')->setToken(null);
$this->get('request')->getSession()->invalidate();
$response = new RedirectResponse($this->generateUrl('dn_send_me_the_bundle_confirm', array(
'token' => $token
)));
// Clearing the cookies.
$cookieNames = [
$this->container->getParameter('session.name'),
$this->container->getParameter('session.remember_me.name'),
];
foreach ($cookieNames as $cookieName) {
$response->headers->clearCookie($cookieName);
}
In Symfony 4/5 this is just enough to remove user:
/**
* #var TokenStorageInterface $token_storage
*/
private TokenStorageInterface $token_storage;
/**
* Will force logout from system
*/
public function logoutCurrentlyLoggedInUser()
{
$this->token_storage->setToken(null);
}
Now You can create a method to use it later to check if user is logged in:
class Application extends AbstractController {...
/**
* Returns currently logged in user
* #return object|UserInterface|null
*/
public function getCurrentlyLoggedInUser()
{
return $this->getUser();
}
In case you are using symfony 4.x (I haven't tested other versions, so it still might work), you may want to use the internal logout handler of symfony (highly recommended, as it will take care of everything for you in a clean way, cookies and all). You don't need to write too much code for that either, you can simply emulate a logout request:
... // Some code, that leads you to force logout the user
// Emulating logout request
$logoutPath = $this->container->get('router')->generate('app_logout');
$logoutRequest = Request::create($logoutPath);
$logoutResponse = $this->container->get('http_kernel')->handle($logoutRequest);
// User is logged out now
... // Stuff to do after logging out, eg returning response
This will make symfony do the request response flow, thus it will call the logout handler internally. This method allows you to proceed to further custom code. Otherwise, if you invoked only the logout listener here, you would have to return the usual logout response, that now is in $logoutResponse. Optionally, if you want to return it, you would also simply:
return $logoutResponse;
The proposed solutions didn't work for me in Symfony 5.3.
It should be something as basic as
session_start();
session_destroy();
So I did this way:
$this->get('session')->start();
$this->get('session')->invalidate();
This will terminate the PHP Session, which is the way most of sessions work in Symfony.

Resources