Automatic user authentication - symfony

Based on this question: Automatic post-registration user authentication
$token = new UsernamePasswordToken($userEntity, null, 'main', array('ROLE_USER'));
$this->get('security.context')->setToken($token);
This worked fine on Symfony 2, but in the actual release of Symfony 2.1 this seams to do not work any more, im trying the same code but with no success (and no errors). The idea is to authenticate my user over ajax without using the login form. This because Im using this schema:
if(user_on_data_base) {
if(user_fb_linked_with_us) { // authenticate him and redirect to /home }
}
This is executed over ajax.
EDIT:
The most strange thing is that if I execute this code from my user controller it works! but executing it from my ajax controller dont.

Looking at how the HWIOAuthBundle does this, it looks like you need to throw the security.interactive_login event.
Here's how you can do it:
$this->get('event_dispatcher')->dispatch(
SecurityEvents::INTERACTIVE_LOGIN,
new InteractiveLoginEvent($this->container->get('request'), $token)
);

Related

Reset Database before each Test has problems with authentication in functional tests

I am implementing functional tests for my REST-Api. The Api is protected by authorization. For this I chose the json_login provider. So far, so good. Authentication works when accessing in the normal environment via Insomnia.
Now I want functional tests. For that, I create an user via the configured User-class and persist it in the database. Works as expected.
But of course the test only works once as the user already exists in the following tests.
So I tried hautelook/alice-bundle with ResetDatabaseTrait or ReloadDatabaseTrait as well as dmaicher/doctrine-test-bundle.
Both show the same behaviour: The authenticator can not find the newly created user. (EntityUserProvider::loadUserByUsername finds no user)
Apparently the EntityUserProvider seems to use a different "connection" into the database that can not look into the transaction those libraries started.
The entity-manager in my test that is responsible for persisting my user is created either with
protected function setUp(): void {
$kernel = self::bootKernel();
$this->em = $kernel->getContainer()
->get('doctrine')
->getManager();
}
or directly before creating the user with
$em = self::$container->get('doctrine')->getManager();
which seems correct for me. But in any case I get the same result -> "Invalid credentials" because the user can not be found.
Maybe someone out there can point me into the right direction?
After a refreshing break I remembered a detail when I was creating my tests. All the examples did not need a setUp-Method with self:bootKernel() in it. But without it the self::$container property was empty, so I added that to my test-class. Maybe there was the solution to the problem?
I was right!
I am using the Api-Platform package. Therefore my test-class is based in ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase. That class does not have a setUp Method, but inspecting createClient() I noticed that there the kernel is created by calling bootKernel() which also stops any running kernel.
So my setUp() method created a kernel. With that kernel I created my user.
Then I called createClient() to create the test-client for the requests. This killed my initial kernel and creates a new one which then leads to the problems.
Rearranging the statements - first create the client, then get the EntityManager from the now created container and create the User after creating the client solved the problem.
After two days , hooh
when you want to call multiple request, for example if you want at first request you get token and the second you call with this token and check auth, in during this calls if you use use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait trait your data base rest after first call, you have token but database is empty, and second call fail.
So, read again this important part of documentation :
There is one caveat though: in some tests, it is necessary to perform multiple requests in one test, for example when creating a user via the API and checking that a subsequent login using the same password works. However, the client will by default reboot the kernel, which will reset the database. You can prevent this by adding $client->disableReboot(); to such tests. Writing Functional Tests
I, know we are lazy developer and first read code, not document :-)
$client = static::createClient();
$client->disableReboot();
$manager = self::getContainer()->get('doctrine')->getManager();
$user = new User();
$user->setEmail('user#example.com');
$user->setPassword(
self::getContainer()->get('security.user_password_hasher')->hashPassword($user, $password = 'pass1234')
);
$manager->persist($user);
$manager->flush();
$response = $client->request('POST', '/authentication-token', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'email' => $user->getEmail(),
'password' => $password ,
],
]);
$token = $response->toArray()['token'] ?? null;
$client->request('GET', '/profile', [
'auth_bearer' => $token
]);
self::assertResponseIsSuccessful();

How to handle exception inside an EventListener class in Symfony

On the onRegistrationSuccess method of an EventListener class, I have an api call that registers a user into another system.
$response = $this->service->postCustomer($customer)
if (200 !== $response->getCode() {
$response = new RedirectResponse($event->getRequest()->getPathInfo());
$event->setResponse($response);
}
My question is, what is the best way to catch the error or display it on the same page? example the route is on /user/register the response is a validation error. Cause right now it still redirects the use to the check email page.
Btw im using FOSUserBundle. Im really new in Symfony so im really not aware on what's the best way to do it
Thank you!
You need to increase the priority of your listener as described in the documentation, otherwise the listener responsible for email confirmation page fires earlier
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => [
['onRegistrationSuccess', 10],
],
];
}
You can use flash messages to show the error as was suggested by #MohamedRadhiGuennichi
Subscribing to FOSUserEvents::REGISTRATION_SUCCESS means that the user is already stored in your database, so you should rollback this in case of an error.

ASP.NET Unauthorized access on a controller should return 401 instead of 200 and the login page

In an ASP.NET 5 Application I configured MVC and Identity framework like this:
app.UseMvc(config=>{
config.MapRoute("Default", "{controller}/{action}/{id?}", new
{
controller = "Home",
action = "Index"
});
});
and adding Identity Services :
services.AddAuthentication();
services.AddAuthorization();
services.AddIdentity<CrmUser, CrmUserRole>(config => {
config.User.RequireUniqueEmail = true;
})
.AddUserStore<MongoUserStore>()
.AddRoleStore<MongoUserStore>()
.AddDefaultTokenProviders();
and
app.UseIdentity()
.UseCookieAuthentication(i => { i.LoginPath = "/Account/Login";});
The example is defined as this:
public class MyApiController : Microsoft.AspNet.Mvc.Controller
{
[Authorize]
public async Task<ActionResult> Foo()
{
return Ok();
}
}
This works fine, but i also have some controller which I want to use in a API way. In ASP.NET 5, they all have same base class so there is no difference between API and View Controllers.
As a result when calling an unauthorized api which requires authorization, I get an HTTP 200 and the Login page instead of an HTTP 401.
In a blog post by Shawn Wildermuth I found this
services.AddCookieAuthentication(config =>
{
config.LoginPath = "/Auth/Login";
config.Events = new CookieAuthenticationEvents()
{
OnRedirect = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") &&
ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult<object>(null);
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
}
};
});
But should this really be the expected way to do this? For me this smells a little.
This issue has been fixed in RC1.
Check GitHub comment here: Issue
To upgrade to RC1, go to http://get.asp.net.
To be even more sure, you can also clear your %userprofile%\.dnx\ folder prior to getting the latest version.
One solution to this is send the request with a the following header:
[{"key":"X-Requested-With","value":"XMLHttpRequest"}]
Here is an example using Postman without the header returns 200 and login webpage html
With the header returns 401 and no content (note there is not a tick next to it)
The sample app is based on the ASP.NET Core default solution with Individual User Authentication
Why this works:
CookieAuthenticationEvents.OnRedirectToLogin() uses IsAjaxRequest(context.Request) to decide whether to return a 401 or a redirect. So you have to have a header that will make IsAjaxRequest(context.Request) return true. It appears like a lot of JavaScript libraries add this automatically for you if you call your API through AJAX, but you might be not be using them.
Related questions you might find helpful
ASP.NET MVC - What does IsAjaxRequest() actually mean?
Request.IsAjaxRequest() always returning false in MVC4, tried all suggestions in SO, etc
Detecting IsAjaxRequest() with ASP.NET MVC and JQuery Form Plugin / File Upload
This behaviour was introduced as a result of a request the OP put in (which they mention under the question)
link to the current version of CookieAuthenticationEvents.cs at time of writing.
One (work around) option is to make the coockie options to redirect you to a controller action returning (unauthorized) result
Sorry I'm replying using my phone I'll try to enhance my answer when I'm using my PC...

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);
}

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