3
votes

I'm facing big issue while implementing FOSFacebookBundle.

I followed the docs and have following situation: * when user clicks login a popup appears * after user grants permission to the app, FB button is being changed (to Logout)

However, my custom provider is not called (only a constructor is called) - yes, I use a noobish debug method (creating empty files with the name of the class method :-)).

Anybody has any suggestion why? Any tips?

Edit
After some time of trying to solve that issue, I feel I'm lost.

Once again, here's my configuration:

app/config/config.yml:

fos_facebook:
    file:   %kernel.root_dir%/../vendor/facebook/src/base_facebook.php
    alias:  facebook
    app_id: xxx
    secret: xxx
    cookie: true
    permissions: [email, user_location]

app/config/routing.yml:

_security_login:
    pattern: /login
    defaults: { _controller: TestBundle:Main:login }

_security_check:
    pattern:  /login_check
    defaults: { _controller: TestBundle:Main:loginCheck }

_security_logout:
    pattern:  /logout
    defaults: { _controller: TestBundle:Main:logout }

app/config/security.yml

security:
    factories:
        -"%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"
    providers:
        my_fos_facebook_provider:
            id: my.facebook.user
        fos_userbundle:
            id: fos_user.user_manager
firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                login_path: /login
                check_path: /login_check
            logout:       true
            anonymous:    true

        public:
            pattern:   ^/.*
            fos_facebook:
                app_url: "http://www.facebook.com/apps/application.php?id=xxx"
                server_url: "http://symfonytest.com.dev/app_dev.php/"
                login_path: /login
                check_path: /login_check
                provider: my_fos_facebook_provider
                default_target_path: /
            anonymous: true
            logout: true

I'm also implementing code into twig template as shown in docs (also implemented snippet from @Matt).

1

1 Answers

6
votes

I have the same workflow as you and my custom user provider is called correctly and everything is working fine.

The first thing that you need to check is: do you have a JavaScript script that redirects the user to the login_check route after it has successfully login into Facebook via the popup? This is important because calling the login_check route after a valid authentication will trigger the security mechanism of Symfony2 that will call the FOSFacebookBundle special security code that will then call your own custom user provider. I think you may be just missing this small piece.

Here the pieces of JavaScript code required to make it work (using jQuery):

$(document).ready(function() {
    Core.facebookInitialize();
}); 

var Core = { 
    /**
     * Initialize facebook related things. This function will subscribe to the auth.login
     * facebook event. When the event is raised, the function will redirect the user to
     * the login check path.
     */
    facebookInitialize = function() {
        FB.Event.subscribe('auth.login', function(response) {
            Core.performLoginCheck();
        });
    };

    /**
     * Redirect user to the login check path.
     */
    performLoginCheck = function() {
        window.location = "http://localhost/app_dev.php/login_check";
    }
}

I put here my security.yml just to help you check for differences with your own file:

security:
    factories:
    - "%kernel.root_dir%/../vendor/bundles/FOS/FacebookBundle/Resources/config/security_factories.xml"

  providers:
    acme.facebook_provider:
      # This is our custom user provider service id. It is defined in config.yml under services
      id: acme.user_provider

  firewalls:
    dev:
      pattern:  ^/(_(profiler|wdt)|css|images|js)/
      security: false

    public:
      pattern:   ^/
      fos_facebook:
        app_url: "http://www.facebook.com/apps/application.php?id=FACEBOOK_APP_ID"
        server_url: "http://localhost/app_dev.php/"
        default_target_path: /
        login_path: /login
        check_path: /login_check
        provider: acme.facebook_provider
      anonymous: true
      logout:    true

And my service definition for the custom user provider we use:

services:
  acme.user_provider:
    class: Application\AcmeBundle\Security\User\Provider\UserProvider
    arguments:
      facebook:      "@fos_facebook.api"
      entityManager: "@doctrine.orm.entity_manager"
      validator:     "@validator"

You also need to create a new route for the /login_check, /login and /logout paths. Those route will be hooked by Symfony2 for the security process. Here an example of the implementation of the actions in a controller called MainController in my cases:

<?php

namespace Application\AcmeBundle\Controller;

use ...;

class MainController extends Controller
{
    /**
     * This action is responsible of displaying the necessary informations for
     * a user to perform login. In our case, this will be a button to connect
     * to the facebook API.
     *
     * Important notice: This will be called ONLY when there is a problem with
     * the login_check or by providing the link directly to the user.
     *
     * @Route("/{_locale}/login", name = "_security_login", defaults = {"_locale" = "en"})
     */
    public function loginAction()
    {
        if ($this->request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
            $error = $this->request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
        } else {
            $error = $this->request->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
        }
        return $this->render('AcmeBundle:Main:login.html.twig', array(
            'error' => $error
        ));
    }

    /**
     * This action is responsible of checking if the credentials of the user
     * are valid. This will not be called because this will be intercepted by the
     * security component of Symfony.
     *
     * @Route("/{_locale}/login_check", name = "_security_check", defaults = {"_locale" = "en"})
     */
    public function loginCheckAction()
    {
        // Call intercepted by the Security Component of Symfony
    }

    /**
     * This action is responsible of login out a user from the site. This will
     * not be called because this will be intercepted by the security component
     * of Symfony.
     *
     * @Route("/{_locale}/logout", name = "_security_logout", defaults = {"_locale" = "en"})
     */
    public function logoutAction()
    {
        return $this->redirect('index');
    }
}

Hope this help, if you have more questions or I misunderstand something from your problem, don't hesitate to leave a comment.

Regards,
Matt