Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I integrate Facebook SDK login with cakephp 2.x?

There seems to be very few to no up to date resources on integration of Facebook login with the cakephp Auth component online. I have found the following resources:

  1. Old Bakery Article using cakephp 1.3? and an older version of Facebook SDK
  2. Cakephp Plugin by webtechnick that seems to be in development

Other than this I found no definitive resources. I wanted the integration to be as flexible (without the use of a magic plugin) as possible. So after much research I finally baked a decent solution which I am sharing here today. Please contribute as I am rather new to cake.

like image 706
shxfee Avatar asked Aug 03 '13 22:08

shxfee


1 Answers

Integration of Cakephp 2.x Auth with Facebook Auth for seamless user authentication

To start off you should read up on the fantastic cakePHP Auth Component and follow the Simple Authentication and Authorization Application tutorial from the cakephp book 2.x (Assuming you have also followed the first two tutorials from the series. After you are done, you should have managed to build a simple cakePHP application with user authentication and authorization.

Next you should download the facebook SDK and obtain an App ID from facebook.


First we will copy the Facebook sdk in to App/Vendors. Then we will import and initialize it in the AppController beforeFilter method.

//app/Controller/AppController.php

public function beforeFilter() {
    App::import('Vendor', 'facebook-php-sdk-master/src/facebook');
    $this->Facebook = new Facebook(array(
        'appId'     =>  'App_ID_of_facebook',
        'secret'    =>  'App_Secret'

    ));

    $this->Auth->allow('index', 'view');
}

We are initializing the Facebook SDK in AppController so that we will have access to it through out the application. Next we will generate the Facebook login URL using the SDK and pass it to the view. I normally do this in the beforeRender method.

Note: The above configuration details (appId & secret) should preferably be saved in App/Config/facebook.php. You should then use cake Configure.

//app/Controller/AppController.php

public function beforeRender() {
    $this->set('fb_login_url', $this->Facebook->getLoginUrl(array('redirect_uri' => Router::url(array('controller' => 'users', 'action' => 'login'), true))));
    $this->set('user', $this->Auth->user());
}

We will update our layout so that we can display this link to facebook login for all users who have not logged in. Notice how we have set redirect_uri to our applications User/login action. This is so that once facebook has authenticated a user, we can log him in using cake::Auth as well. There are various benefits to this, including the solution for this question.

<!-- App/Views/Layouts/default.ctp just after <div id="content"> -->
<?php
    if($user) echo 'Welcome ' . $user['username'];
    else {
        echo $this->Html->link('Facebook Login', $fb_login_url) . ' | ';
        echo $this->Html->link('Logout', array('controller' => 'user', 'action' => 'logout'));
?>

When the user clicks the login link, facebook SDK will login the user and redirect them to our app Users/login. We will update this action for handling this:

// App/Controller/UsersController.php
// Handles login attempts from both facebook SDK and local
public function login()
{
    // If it is a post request we can assume this is a local login request
    if ($this->request->isPost()){
        if ($this->Auth->login()){
            $this->redirect($this->Auth->redirectUrl());
        } else {
            $this->Session->setFlash(__('Invalid Username or password. Try again.'));
        }
    } 

    // When facebook login is used, facebook always returns $_GET['code'].
    elseif($this->request->query('code')){

        // User login successful
        $fb_user = $this->Facebook->getUser();          # Returns facebook user_id
        if ($fb_user){
            $fb_user = $this->Facebook->api('/me');     # Returns user information

            // We will varify if a local user exists first
            $local_user = $this->User->find('first', array(
                'conditions' => array('username' => $fb_user['email'])
            ));

            // If exists, we will log them in
            if ($local_user){
                $this->Auth->login($local_user['User']);            # Manual Login
                $this->redirect($this->Auth->redirectUrl());
            } 

            // Otherwise we ll add a new user (Registration)
            else {
                $data['User'] = array(
                    'username'      => $fb_user['email'],                               # Normally Unique
                    'password'      => AuthComponent::password(uniqid(md5(mt_rand()))), # Set random password
                    'role'          => 'author'
                );

                // You should change this part to include data validation
                $this->User->save($data, array('validate' => false));

                // After registration we will redirect them back here so they will be logged in
                $this->redirect(Router::url('/users/login?code=true', true));
            }
        }

        else{
            // User login failed..
        }
    }
}

And we are done! Most of the heavy lifting is done by this action as you can see. You should preferably move some of the above code in to UserModel. So here's a summary of what is going on.

At first we check if the login request is send from the login form of our application @ Users/login. If it is, then we simply log the user in. Otherwise we check if the user exists in our database and if he does log him in or create a new user, and then log him in.

Be careful to verify the user here with more than their email, like their facebook_id. Otherwise there is a chance the user could change their facebook email and hijack another user of your application.

Happy Coding!

like image 79
shxfee Avatar answered Oct 14 '22 11:10

shxfee