The Starting Point

OAuth in Symfony 2

Setting up HWIOAuth Bundle

1. Add the HWI OAuth bundle to the project

Download the OAuthBundle from the official git repository and copy the contents to ProjectRoot/vendor/bundles/HWI/Bundle/OAuthBundle

2. Enable the bundle in AppKernel.php

AppKernel
1
2
3
4
5
6
7
8
9
10
11
public function registerBundles()
    {
        $bundles = array(
            .............................
            new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
        );

        .................................

        return $bundles;
    }

HWI OAuth bundle needs Sensio Buzz bundle for proper functioning. So, add the buzz bundle to your project if it is not already added. You may download it from here. If it is not added, you may get an error message like this

Error
1
 Fatal error: Class 'Buzz\Client\Curl' not found in /home/xxxx/my-projects/OAuthProject/app/cache/dev/appDevDebugProjectContainer.php on line 2110 Call Stack: 0.0001 321828 1

To avoid this, copy the contents of buzz.zip atached along with this to PROJECT_ROOT/vendor

Also, add the namespace for the OAuth bundle and the Buzz bundle to autoload.php

Autoload.php
1
2
3
4
 $loader->registerNamespaces(array(
   'Buzz'=>__DIR__.'/../vendor/buzz/lib',
    'HWI'=>__DIR__.'/../vendor/bundles',
 ));

Register the buzz bundle namespace before HWI bundle’s namespace to avoid this error

Error
1
 "Fatal error: Class 'Buzz\Client\Curl' not found"

3. Import the routing file of HWI bundle to the projects routing.yml

Routing.yml
1
2
3
 hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect

4. Import the security_factory.xml to security section of security.yml

security.yml
1
2
3
security:
    factories:
        - "%kernel.root_dir%/../vendor/bundles/HWI/Bundle/OAuthBundle/Resources/config/security_factory.xml"

5.Add the resource owners in the config.yml. For this, we need to register our app in Google and Facebook and obtain the client_id and the client_secret. In addition to that, we have to provide a redirect URL while configuring the app. The response after authenticating the user by the service providers is sent to this URL.

For Google, this redirect URL is www.yourdomain.com/login/check-google. This address should be provide against the field labelled ‘Redirect URIs’. Please see the screenshot below. google api

For Facebook, once you register the app, click on ‘Add platform’ button and register your app as a web app. The redirect URL for Facebook is www.yourdomain.com/login/check-facebook. Enter this URL in ‘Site URL’ field and ‘Mobile Site URL ’ field under ‘Website’. Please see the screenshot below. facebook api

Then, provide the client_id and client_secret for Facebook and Google in the config.yml file

config.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 hwi_oauth:
     #name of the firewall in which this bundle is active, this setting MUST be set

    firewall_name: main(What ever firewall name that you have specified in security.yml)
    target_path_parameter: /
    resource_owners:
        facebook:
            type:                facebook
            client_id:           48000000093708
            client_secret:       cda9csdsdsds8dc4dc0699448385c9016c7
            scope:               "email"

        google:
            type:                google
            client_id:           872410845454.apps.googleusercontent.com
            client_secret:       _rFBqWyAGHFvbdN-EgUs52uGK2Z
            scope:               "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"


6.Create a User Provider Service
The bundle needs a service that is able to load users based on the user response of the oauth endpoint and the service should implement
HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface.
For this, first create a class caller XYZOAuthUserProvider .

OAuthUserProvider.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace XYZ\CoreBundle\Services;

use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;


class XYZOAuthUserProvider extends OAuthUserProvider
{


}

Then, register it as a service in services.yml file

services.yml
1
2
3
4
5
xyz.oauth.user_provider:
    class: XYZ\CoreBundle\Services\XYZOAuthUserProvider
    tags:
      - { name: user.provider }
    arguments: ["@service_container"]

7. Modifications in the security.yml file

1. Under Providers, add the service created in the previous step
security.yml
1
2
3
4
providers:
    main:
        entity: { class: XYZCoreBundle:User }
    my_custom_hwi_provider: { id: tsz.oauth.user_provider }
2. Under Firewalls/main, add
security.yml
1
2
3
4
5
6
7
8
9
oauth:
        resource_owners:
            facebook:           "/login/check-facebook"
            google:             "/login/check-google"
        login_path:        /connect/facebook
        failure_path:      /login
        default_target_path: /login
        oauth_user_provider:
            service: xyz.oauth.user_provider

8. Import the login check routes corresponding to each resource owner in the routing.yml file

routing.yml
1
2
3
4
5
   facebook_login:
        pattern: /login/check-facebook

    google_login:
        pattern: /login/check-google

Checking whether the OAuth user exists in the local database

By default, a user who logs in through facebook or google is given ROLE_USER, and ROLE_OAUTH_USER. In this case, even though all the details required for a registered user are not available from Google or Facebook, the user still have all the privileges of ROLE_USER. This should not be the case. The user should not be granted all the provileges unless he completes his profile. So, for a user who logs in through Google or Facebook is given only ROLE_OAUTH_USER if he is not present in the database. If he is present in the database, he is granted ROLE_USER when he logs in through Google or Facebook. So, there should be a mechanism to check whether the OAUth user is present in the database or not.

Step 1
Edit vendor/bundles/HWI/Bundle/OAuthBundle/Security/Core/User/OAuthUser.php and modify the getRoles() function to return only ROLE_OAUTH_USER as role.

Original function:

Original function
1
2
3
4
public function getRoles()
    {
        return array('ROLE_USER', 'ROLE_OAUTH_USER');
    }

Modified function:

Modified function
1
2
3
4
public function getRoles()
    {
        return array('ROLE_OAUTH_USER');
    }

Step 2

Next, edit the OAuthUserProvider.php file

  • Import the namespace of the bundle containing the User entity to OAuthUserProvider.php so that we can get the container inside OAuthUserProvider.php.
OAuthUserProvider.php
1
use Venom\XYZBundle\XYZBundle ;
  • In the original configuration loadUserByOAuthUserResponse() function calls the loadUserByUsername() function with nickname as the arguement. The nickname is obtained from the server response. In our entity user provider, we have defined email as a unique field. So, to check whether user exists in the database, we can use email. For this, we need to obtain the email from the response.

Although UserResponseInterface class has a getEmail() method, this won’t return the email address for all the service providers as the response for each service provider is different

A var_dump() for the response object for Facebook and Google are given below

Facebook:

Facebook Response Dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
["paths":protected]=>
  array(5) {
["identifier"]=>
string(2) "id"
["nickname"]=>
string(8) "username"
["realname"]=>
string(4) "name"
["email"]=>
NULL
["profilepicture"]=>
NULL
  }
  ["response":protected]=>
  array(13) {
["id"]=>
string(15) "1XXXX5898097313"
["name"]=>
string(12) "XXXXXX"
["first_name"]=>
string(9) "XXXXX"
["last_name"]=>
string(2) "YY"
["link"]=>
string(40) "https://www.facebook.com/xxxx.yyy"
["username"]=>
string(15) "xxxx.yyy"
["birthday"]=>
string(10) "04/14/1900"
["gender"]=>
string(4) "male"
["email"]=>
string(20) "xxxx@yyyy.com"
["timezone"]=>
float(5.5)
["locale"]=>
string(5) "en_US"
["verified"]=>
bool(true)
["updated_time"]=>
string(24) "2013-07-19T09:59:22+0000"
  }

Google:

Google response dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  ["paths":protected]=>
  array(5) {
    ["identifier"]=>
    string(2) "id"
    ["nickname"]=>
    string(4) "name"
    ["realname"]=>
    string(4) "name"
    ["email"]=>
    string(5) "email"
    ["profilepicture"]=>
    string(7) "picture"
  }
  ["response":protected]=>
  array(8) {
    ["id"]=>
    string(21) "10493450YYYY981597842"
    ["email"]=>
    string(28) "praveesh@abc.in"
    ["verified_email"]=>
    bool(true)
    ["name"]=>
    string(10) "Praveesh A"
    ["given_name"]=>
    string(8) "Praveesh"
    ["family_name"]=>
    string(1) "A"
    ["locale"]=>
    string(2) "en"
    ["hd"]=>
    string(19) "abc.in"
  }

From the variable dump, we can see that the email address can be obtained from the response by

1
2
 $user_details= $response->getResponse();
 $email = $user_details['email'];

`

  • Edit loadUserByOAuthUserResponse() function as given below
1
2
3
4
5
6
7
8
9
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $user_details= $response->getResponse();
    $email = $user_details['email'];

    return $this->loadUserByUsername($email);


    }

3. To check whether the user exists in the database, edit loadUserByUsername($username) function as given below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function loadUserByUsername($username)
{
    //get the entity manager
    $em = CMSBundle::getContainer()->get('doctrine')->getEntityManager();

    //load the user from DB
    $entity = $em->getRepository('XYZBundle:User')->loadUserByUsername($username);

    if(!$entity){
        //if the user does not exist, then return a new OAuth user
        $user = new OAuthUser($username);
     } else {
         // if the user exists, return that user
        $user = $entity;
    }
    return $user;

}

4. Then, hide or show options in the twig file according to the role of the user.

Twig
1
2
3
4
5
6
7
8
9
    <div class="well">
        {% if is_granted('ROLE_USER') %}
            OPTIONS THAT REQUIRE ROLE_USER PRIVILEGE
        {% else %}
            PROMPT TO REGISTER TO AVAIL ALL FEATURES

        {% endif %}

    </div>

Comments