Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Login Facebookuser into Symfony per AJAX #551

Closed
fnordo opened this issue May 7, 2014 · 27 comments
Closed

Login Facebookuser into Symfony per AJAX #551

fnordo opened this issue May 7, 2014 · 27 comments

Comments

@fnordo
Copy link

fnordo commented May 7, 2014

Hello,

i am currently playing around with this bundle and was wondering if it is possible to trigger the symfony-login after a successful facebook-login (via the facebook login button) per AJAX.

If i understand this example right, in the most cases one would do the following to log the user into the symfony application:

  • Make sure that the facebook javascript SDK is loaded
  • When the user clicks on the facebook login button (and the user has not authorized the app) call FB.login()
  • After a successful login on the facebook-side, trigger the symfony login by issuing a GET to the URL defined for the hwi_oauth_service_redirect-route
  • Then the bundles ConnectController::redirectToServiceAction() is executed on the serverside which results in a redirect to facebooks oauth endpoint to get the oauth code (which in turn is required to log the user successfully into the symfony application).

The problem i am facing now is that i am working on a single page application where the page must not be reloaded. So my first naive approach was to - in the FB.login-callback - issue the GET to hwi_oauth_service_redirect (resp. login/facebook) per AJAX like the following:

$.ajax({
  url: 'login/facebook',
  type: 'GET',
  dataType: 'json',
  crossDomain: true,
})
.done(function(data, textStatus, jqXHR){
  console.info(data)
})
.fail(function(jqXHR, textStatus, errorThrown){
  console.warn('Could not login facebook user')
});

When i try to do this, unfortunately i run into cross-domain-issues and Firebug shows the following message:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.facebook.com/dialog/oauth?response_type=code&client_id=MY_APP_CLIENT_ID&scope=emall&redirect_uri=MY_REDIRECT_UR/login/check-facebook&display=popup. This can be fixed by moving the resource to the same domain or enabling CORS.

In contrast when i call the related URI (MY_DOMAIN/login/facebook) by reloading the page as shown in the docs everything works fine and the user is logged in on both sides (facebook and my symfony app).

As i am not sure if am doing everything right or if i am missing something here, my question is: is it possible to trigger the symfony-login as described above (per AJAX) and am i just missing something or is it just not possible to do this?

My configuration of the bundle looks something like the following:

# app/config/config.yml

hwi_oauth:
  connect:
      account_connector: my_custom_oauth_userprovider_service
  firewall_name: main
  fosub:
      username_iterations: 5
      properties:
         facebook: facebookId
  resource_owners:
    facebook:
      type:           facebook
      client_id:      MY_APP_CLIENT_ID
      client_secret:  MY_APP_CLIENT_SECRET
      scope:          "email"
      options:
          display: popup 
      infos_url:     "https://graph.facebook.com/me?fields=username,name,email,picture.type(square)"
      paths:
          email:          email
          profilepicture: picture.data.url

# app/config/security.yml

security:
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

    firewalls:
        main:
            pattern: ^/
            form_login:
                success_handler: my_custom.authentication_success_handler
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
            logout:
              path: /logout
              success_handler: my_custom.security.handler.logout
            anonymous:    true

            # HWIOAuth-related configuration:
            oauth:
                resource_owners:
                    facebook: "/login/check-facebook"
                login_path:        /login
                use_forward:       false
                failure_path:      /login
                success_handler: my_custom.authentication_success_handler
                oauth_user_provider:
                  service: my_custom.oauth_user_provider

        # some configuration for other firewalls...

    access_control:
          - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
          # more access_control stuff...

# app/config/routing.yml

hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /login

facebook_login:
    pattern: /login/facebook

# imported FOS-UserBundle routes and custom routes... 
@guilemos
Copy link

guilemos commented May 8, 2014

Hello, I've same problem! I thinking to use iframe instead ajax call.

function statusChangeCallback(response) {
    if (response.status === 'connected') {
        fbMe(ESC.Interface.loginUser);
        fbMePic(ESC.Interface.loginUserPic);

      var iframe = document.createElement('iframe');
      iframe.src = Routing.generate("hwi_oauth_service_redirect", {service: "facebook"});

      $(iframe).appendTo('body').ready(function(){
        console.log(iframe.contentDocument);
      });

    } else if (response.status === 'not_authorized') {

    } else {

    }
}   

or something like that...

@asennoussi
Copy link

did you fix your problem ?

@fnordo
Copy link
Author

fnordo commented Nov 8, 2014

@Sshuichi
First i tried to solve the problem with the iframe-solution proposed by @guilemos, which really worked for me primarily.

Unfortunately, after a couple of days the facebook login was broken (although i did not make any changes to the related code): Suddenly, when trying to log in to the app via facebook, the login failed and the firefox console gave me an error similar to the one mentioned in this post

Finally i gave up and ended up in implementing a custom solution without using the HWIOAuthBundle, which is a pity, cause this is really a great bundle (thx a lot to all the contributers for the work they put into this btw).

Anyway, i am still curious if there is the possibility to do such a thing using this bundle, but unfortunately till today i don´t know if this is simply not possible or if i just missed something on how to do it : (

@asennoussi
Copy link

Hi ! I solved it and this is how I did it. (working example on Neargood.com)
First of all you need a service that listens to login operations and you need to configure your firewall as follow :

firewalls:
    secured_area:
        oauth:
            resource_owners:
                facebook:           "/a/login/check-facebook"
            login_path:        /a/login
            use_forward:       false
            failure_path:      /a/login
            oauth_user_provider:
                service: ng.user.auth.provider
            success_handler: ng.security.authentication_handler
            failure_handler: ng.security.authentication_handler

now let's set our service in services.yml

ng.security.authentication_handler:
    class: ng\MyBundle\Controller\Listeners\AuthenticationHandler
    public: false
    arguments:
        - @router
        - @session
        - @security.context

and finally this is what you should put in your class AuthenticationHandler:

<?php
namespace ng\MyBundle\Controller\Listeners;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;

class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
    private $router;
    private $session;

    /**
     * Constructor
     *
     * @author     Joe Sexton <[email protected]>
     * @param     RouterInterface $router
     * @param     Session $session
     */
    public function __construct( RouterInterface $router, Session $session,SecurityContextInterface $context )
    {
        $this->router  = $router;
        $this->session = $session;
        $this->context = $context;
    }

    /**
     * onAuthenticationSuccess
      *
     * @author     Joe Sexton <[email protected]>
     * @param     Request $request
     * @param     TokenInterface $token
     * @return     Response
     */
    public function onAuthenticationSuccess( Request $request, TokenInterface $token )
    {
        if(!$token->getUser()->getName()) //if user doesn't have username redirect to form
        {   
            $accessToken=$token->getUser()->getAccessToken();
            $this->context->setToken(null);
            $request->getSession()->invalidate();
            $redirectUrl= $this->router->generate("RegisterContinue", array("access_token" => $accessToken));
             return new RedirectResponse( $redirectUrl );
        }
        // if AJAX login
        if ( $request->isXmlHttpRequest() ) {

            $array = array( 'success' => true ); // data to return via JSON
            $response = new Response( json_encode( $array ) );
            $response->headers->set( 'Content-Type', 'application/json' );

            return $response;

        // if form login 
        } else {

            if ( $this->session->get('_security.main.target_path' ) ) {

                $url = $this->session->get( '_security.main.target_path' );

            } else {

                $url = $this->router->generate( 'Welcome' );

            } // end if

            return new RedirectResponse( $url );

        }
    }

    /**
     * onAuthenticationFailure
     *
     * @author     Joe Sexton <[email protected]>
     * @param     Request $request
     * @param     AuthenticationException $exception
     * @return     Response
     */
     public function onAuthenticationFailure( Request $request, AuthenticationException $exception )
    {
        // if AJAX login
        if ( $request->isXmlHttpRequest() ) {

            $array = array( 'success' => false, 'message' => $exception->getMessage() ); // data to return via JSON
            $response = new Response( json_encode( $array ) );
            $response->headers->set( 'Content-Type', 'application/json' );

            return $response;

        // if form login 
        } else {

            // set authentication exception to session
            $request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception);

            return new RedirectResponse( $this->router->generate( 'login_route' ) );
        }
    }
}

@asennoussi
Copy link

For the Js part, leave it as the bundle suggests. If you have any questions please let me know

@fnordo
Copy link
Author

fnordo commented Nov 9, 2014

@Sshuichi
Thx a lot for the detailed description. What exactly do you mean with:

For the Js part, leave it as the bundle suggests

Are you refering to this description from the docs?

@asennoussi
Copy link

Yes I meant the function fb_login body!

@fnordo
Copy link
Author

fnordo commented Nov 9, 2014

@Sshuichi
Ok, i think there is a misunderstanding about what exactly my problem is/was.
To be more specific, this following line of code (which in the example is executed in fb_login()) is whats causing the problem for me:

document.location = "{{ url("hwi_oauth_service_redirect", {service: "facebook"}) }}";

This reloads the webpage, and that is exactly what must not happen in my application

@asennoussi
Copy link

That happens only for registration , for login it will happen without reloading the page, You can check on Neargood.com how it works

@fnordo
Copy link
Author

fnordo commented Nov 9, 2014

@Sshuichi

You can check on Neargood.com how it works

Unfortunately i cannot verify that, as trying to signup with facebook throws a ReferenceError: FB is not defined.

That happens only for registration

Does not work for me, as the page must not be reloaded (neither on signup nor on login)

Anyway, thx for your effort..

@asennoussi
Copy link

I'll check that when I'm back home don't worry we'll fix that

@asennoussi
Copy link

I have just double checked Neargood, are you using any plugin on your browser ?
if yes , disable them and try again

@asennoussi
Copy link

Anyways, you can make it work for the registeration as well, it's the same method but you'll need to make the redirection in your JS after receiving the answer ! I'll wait for your confirmation about Ng's fb login feature

@fnordo
Copy link
Author

fnordo commented Nov 9, 2014

Sry, my bad. In fact a browser-plugin was blocking facebook on your site ; )

@asennoussi
Copy link

Good! now the global method (services) is in front of you, you can do whatever you want with it!
Personally I don't think reloading a page (once in a while is a bad experience)
Happy coding!

@vazgen
Copy link

vazgen commented Dec 18, 2014

but seems it is being redirected to an other page. on my website http://www.anagrammer.com/ I did by iframe but I do not like it.

@nairbeau
Copy link

Hello,
Sorry, but I don't understand the solution. I posted a question on Stack Overflow (http://stackoverflow.com/questions/27585404/login-user-with-hwioauthbundle-in-ajax). To sum up, I want to authenticate in AJAX and then if it's ok get the response to POST a form. Can you ask it please because I don't know how implement it ?

@hugomn
Copy link

hugomn commented Jan 15, 2015

Hey, @Sshuichi. I could not understand how you did an AJAX request after receiving facebook response. The bundle suggestion in fb_init() method will always redirect the page. Can you clarify?

I'm trying to achieve exaclty the same here. I was using FOSFacebookBundle, and I was able to do facebook ajax login successfully. Now, as FOSFacebookBundle is deprecated, I'm trying to achieve the same with HWIOAuthBundle. I've spent a lot of hours today, and I realize FOSFacebookBundle was able to intercept a request to _security_check route, and then, I was able to call $.post('_security_check') and get my response in my AuthenticationHandler. So, in my AuthenticationHandler I could do a check like:

if ($request->isXmlHttpRequest()) {
  // return json
}

I'm afraid HWIOAuthBundle developers have not thought about a route to verify authorization usign XmlHttpRequest (ajax). Today, the only way to finish authorization is doing a redirect to hwi_oauth_service_redirect route. And as we see in this post, having a ajax endpoint is really important for many situations, like SPAs.

@stloyd I'd really appreciate if you can guide us better here. Is it possible today? How hard do you think it is to implement? I can try to help. I think this job was done in FOSFacebookBundle by this code: https://github.com/FriendsOfSymfony/FOSFacebookBundle/blob/master/Security/EntryPoint/FacebookAuthenticationEntryPoint.php. Maybe @lsmith77 or @kriswallsmith can help us clarify here. Is it an option to fork FOSFacebookBundle?

@hugomn
Copy link

hugomn commented Jan 23, 2015

Nobody? =/

@vazgen
Copy link

vazgen commented Jan 23, 2015

I made it work on http://www.anagrammer.com/, using iframe but it is not the best solution

@hugomn
Copy link

hugomn commented Jan 23, 2015

Hey @vazgen, can you share your iframe solution? Thanks in advance! =)

@vazgen
Copy link

vazgen commented Jan 23, 2015

Hey @hugomn I can share it but I use angularjs ! would it be ok?

@hugomn
Copy link

hugomn commented Jan 23, 2015

No problem, @vazgen. I just want to check how you get the results from the iframe. =)

@vazgen
Copy link

vazgen commented Jan 23, 2015

let's say user clicks on facebook button, I create an iframe and append it to document. After that I check ones per each second (using $interval ) and if user has been authenticated I stop $interval something like

element.bind('click', function ($event) {

                    $event.preventDefault();

                    $facebook.login().then(function (response) {
                        if (response.status === 'connected') {
                            //fbMe(ESC.Interface.loginUser);
                            //fbMePic(ESC.Interface.loginUserPic);

                            $rootScope.$broadcast('user-loading', true);
                            DialogService.close();

                            var iframe = document.createElement('iframe');
                            iframe.src = basePath + "my/connect/facebook";
                            iframe.width = anIframe.width;
                            iframe.height = anIframe.height;
                            iframe.style.border = 0;
                            iframe.style.outline = 'none';

                            $timeout(function () {
                                $(iframe).appendTo('footer').ready(function () {

                                    var checkLogin = $interval(function () {
                                        FosUserService.user([], function (data) {

                                            if (data.error == undefined) {
                                                $rootScope.$broadcast('user-loading', false);
                                                $interval.cancel(checkLogin);
                                                $rootScope.$broadcast('just-logged', data);
                                            }
                                        });
                                    }, 1000);
                                });
                            }, 100);
                        }
                    }, function (err) {
                    });
                });

the same I use for google plus and native one. Hope this can help.

@hugomn
Copy link

hugomn commented Jan 26, 2015

@vazgen, thanks for your fast reply. I'm trying your solution here, but couldn't understand what your line FosUserService.user does. Is it like a service to check if user is logged via ajax? Thanks, man.

@vazgen
Copy link

vazgen commented Jan 27, 2015

@hugomn , you are right , it is being used in order to check is user logged or not

.factory('FosUserService', ['$resource', 'basePath', function ($resource, basePath) {
    return $resource(basePath + 'my/:action', {}, {
        registration: {
            method: 'POST',
            params: {action: 'pure-register'},
            isArray: false,
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        },
        login: {
            method: 'POST', params: {action: 'login_check'}, isArray: false,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'X-Requested-With': "XMLHttpRequest"
            }
        },
        user: {method: 'POST', params: {action: 'get-user'}, isArray: false}
    });
}])

@hugomn
Copy link

hugomn commented Feb 11, 2015

@vazgen, I could not make it work. When I open a frame and try to open facebook page inside it, I've got CORs errors too. Lost a lot of time with this, and could not find a way to implement ajax login with hwioauthbundle. I'll fork fosfacebookbundle as it was working fine, although hwi is an awesome bundle. Thanks for your help.

@XWB XWB closed this as completed May 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants