Zend_Auth_Adapter_Facebook


This post is rather more nerdy than my other ones, and goes to describe how to create a Facebook authentication adapter for the Zend Framework, using the new Facebook Graph API. I do not intend to support this code explicitly, but I do work with it and will update it to suit my own needs. If you’re impatient, the full code for the adapter and a sample controller are the last two code samples at the bottom of this post.

A discussion about OAuth and OpenID

Strictly speaking, Facebook supports OAuth, not OpenID. OAuth is a way for your application to act on behalf of someone else on another side, while OpenId exists solely for you to use the other website as an authorization authority. The difference is subtle, however telling that Facebook has explicitly stated that it does not want to be an authorization authority. In practice, however, the two standards are interchangeable, and OAuth works pretty well as a way to authenticate a user.

How Facebook Auth Actually Works

Here’s the flow in four easy steps:

  1. The user clicks on a “Login to Facebook” link on your server, which redirects them to another page on your server that handles the authentication.
  2. Your Facebook Auth page puts all the parameters for your authentication, such as your app ID and scope, into a URL and sends the user to Facebook via that URL.
  3. Once Facebook returns the user (presumably authenticated) to your server, you can use the parameters sent via the URL to request an authentication token from Facebook. This is done via curl or some other server-to-server communication by calling the graph API.
  4. You use this authentication token to access the user’s information on Facebook.

Step 1: Get all the data you need

To properly authenticate with Facebook, you need four pieces of data. The first two are your application id and your application secret, both of which are available on your developer application page.

Screen shot 2010-08-21 at 1.50.04 PM.png

A sample Facebook application page

The second two pieces of data are specific to your implementation. These are the redirect URI, which is used to tell Facebook where to send the user once they are done authorizing your application, and the permission scope, which you use to tell Facebook what permissions you want to request from the user. This latter one actually warrants some more documentation, which Facebook has graciously provided here. Be careful: Users can get really suspicious if you ask for too much data.

Step 2: Redirect your user to Facebook.

With this data, you can redirect your user to Facebook. Facebook will then work some magic and ask the user whether they want to grant your application access to their profile. Don’t forget to urlencode that Redirect URI.

$loginUri = "https://graph.facebook.com/oauth/authorize?client_id={$appId}&redirect_uri={$redirectUri}&scope={$scope}";
header('Location: ' . $loginUri);

Step 3: Request an Auth Token

Assuming that the user authorizes your application, Facebook will return them to your redirect URI with the “code” parameter in the URL. You can use this parameter to request an authorization token from Facebook. In this case I’m using the Zend Request handler to simplify things a little.

$client = new Zend_Http_Client( "https://graph.facebook.com/oauth/access_token" );
$client->setParameterGet('client_id', $appId);
$client->setParameterGet('client_secret', $secret);
$client->setParameterGet('code', $code);
$client->setParameterGet('redirect_uri', $redirectUri);

$result = $client->request('GET');
$params = array();
parse_str($result->getBody(), $params);
$token = $params['access_token'];

Step 4: Read the user’s profile

At this point, you have an auth token that grants you access to read the user’s profile information in accordance to the permissions they have granted you. These permissions are completely blind- you don’t have to additionally specify what you want to access – the requests will simply return the information to which you have access.

$client = new Zend_Http_Client( "https://graph.facebook.com/me" );
$client->setParameterGet('client_id', $appId);
$client->setParameterGet('access_token', $token);
$result = $client->request('GET');
$user = json_decode($result->getBody());

Zend_Auth_Adapter_Facebook

For those of you who would rather not take the above code and wrap it into your own Auth Adapter, I’ve gone ahead and done all that work for you. Below you will find a sample Zend Controller, as well as a pre-baked Auth Adapter. If you come across any major security problems in it, I’d love to know, since I use this adapter myself :).

file: Zend/Auth/Adapter/Facebook.php

class Zend_Auth_Adapter_Facebook implements Zend_Auth_Adapter_Interface
{
    /**
     * The Authentication URI, used to bounce the user to the facebook redirect uri.
     * 
     * @var string
     */
    const AUTH_URI = 'https://graph.facebook.com/oauth/authorize?client_id=%s&redirect_uri=%s';
    
    /**
     * The token URI, used to retrieve the OAuth Token.
     * 
     * @var string
     */
    const TOKEN_URI = 'https://graph.facebook.com/oauth/access_token';
    
    
    /**
     * The user URI, used to retrieve information about the user.
     * 
     * @var string
     */
    const USER_URI = 'https://graph.facebook.com/me';
    
    /**
     * The application ID
     *
     * @var string
     */
    private $_appId = null;

    /**
     * The application secret
     *
     * @var string
     */
    private $_secret = null;

    /**
     * The authentication scope (advanced options) requested
     *
     * @var string
     */
    private $_scope = null;

    /**
     * The redirect uri
     *
     * @var string
     */
    private $_redirectUri = null;

    /**
     * Constructor
     *
     * @param string $appId the application ID
     * @param string $secret the application secret
     * @param string $scope the application scope
     * @param string $redirectUri the URI to redirect the user to after successful authentication
     */
    public function __construct($appId, $secret, $redirectUri, $scope)
    {
        $this->_appId = $appId;
        $this->_secret = $secret;
        $this->_scope = $scope;
        $this->_redirectUri   = $redirectUri;
    }

    /**
     * Sets the value to be used as the application ID
     *
     * @param  string $appId The application ID
     * @return Zend_Auth_Adapter_Facebook Provides a fluent interface
     */
    public function setAppId($appId)
    {
        $this->_appId = $id;
        return $this;
    }

    /**
     * Sets the value to be used as the application secret
     *
     * @param  string $secret The application secret
     * @return Zend_Auth_Adapter_Facebook Provides a fluent interface
     */
    public function setSecret($secret)
    {
        $this->_secret = $secret;
        return $this;
    }

    /**
     * Sets the value to be used as the application scope (array())
     *
     * @param  string $scope The application scope
     * @return Zend_Auth_Adapter_Facebook Provides a fluent interface
     */
    public function setApplicationScope($scope)
    {
        $this->_scope = $scope;
        return $this;
    }

    /**
     * Sets the redirect uri after successful authentication
     *
     * @param  string $redirectUri The redirect URI
     * @return Zend_Auth_Adapter_Facebook Provides a fluent interface
     */
    public function setRedirectUri($redirectUri)
    {
        $this->_redirectUri = $redirectUri;
        return $this;
    }


    /**
     * Authenticates the user against facebook
     * Defined by Zend_Auth_Adapter_Interface.
     *
     * @throws Zend_Auth_Adapter_Exception If answering the authentication query is impossible
     * @return Zend_Auth_Result
     */
    public function authenticate()
    {
    	// Get the request object.
    	$frontController = Zend_Controller_Front::getInstance();
    	$request = $frontController->getRequest();
    	
    	// First check to see wether we're processing a redirect response.
    	$code = $request->getParam('code');
    	
    	if ( empty ($code ) )
    	{
	    	// Create the initial redirect
	    	$loginUri = sprintf(Zend_Auth_Adapter_Facebook::AUTH_URI , $this->_appId, $this->_redirectUri);
	 		
	    	if ( !empty($this->_scope) )
	    	{
	    		$loginUri .= "&scope=" . $this->_scope;
	    	}
	    	
	    	header('Location: ' . $loginUri );
    	}
    	else
    	{
    		// Looks like we have a code. Let's get ourselves an access token
	    	$client = new Zend_Http_Client( Zend_Auth_Adapter_Facebook::TOKEN_URI );
	    	$client->setParameterGet('client_id', $this->_appId);
	    	$client->setParameterGet('client_secret', $this->_secret);
	    	$client->setParameterGet('code', $code);
	    	$client->setParameterGet('redirect_uri', $this->_redirectUri);
	    	
	    	$result = $client->request('GET');
	    	$params = array();
	    	parse_str($result->getBody(), $params);
	    	
	    	// REtrieve the user info
	    	$client = new Zend_Http_Client(Zend_Auth_Adapter_Facebook::USER_URI );
	    	$client->setParameterGet('client_id', $this->_appId);
	    	$client->setParameterGet('access_token', $params['access_token']);
	    	$result = $client->request('GET');
	    	$user = json_decode($result->getBody());
	    	
            return new Zend_Auth_Result( Zend_Auth_Result::SUCCESS, $user->id, array('user'=>$user, 'token'=>$params['access_token']) );
    	}
    	
        return new Zend_Auth_Result( Zend_Auth_Result::FAILURE, null, 'Error while attempting to redirect.' );
    }
}

file: modules/default/controllers/FacebookauthController.php

/**
 * Sample Facebook Auth Controller.
 * 
 * @author Michael Krotscheck
 */
class FacebookauthController extends Zend_Controller_Action
{
	/**
	 * Application default action
	 */
	public function indexAction ()
	{
		$appId = 'YOUR_APP_ID';
		$secret = 'YOUR_APP_SECRET';
		$redirectUri = 'http://path-to-this-controller-and-action';
		$scope = 'YOUR_COMMA_SEPARATED_APPLICATION_SCOPE';
		
		// Create the authentication adapter.
		$adapter = new Zend_Auth_Adapter_Facebook( $appId, $secret, $redirectUri, $scope );
		
		// Get an authenticator instance
		$auth = Zend_Auth::getInstance();
		
		// This call will automatically redirect to facebook with the passed parameters.
		$result = $auth->authenticate($adapter);
		
		if ( $result->isValid() )
		{
			// Get the messages
			$messages = $result->getMessages();
			
			// Get the user object from the returned messages.
			$fbUser = $messages['user'];
			
			var_dump($fbUser);
		}
	}
}

15 Comments

  • Looks good, hope it works:-) Thanks for your work!

  • Cory Comer

    This is great, exactly what I was looking for to help round out my OAuth adapters in my own library to complement those of Zend. Thanks for this, little modifications and it's perfect for my use!

  • issamux

    why not using facebook php API it s more easy !!!? is there any reasons for writing a Zend_http client . thanks

  • This is using the Facebook API :)

  • ok :) i use Facebook PHP SDK in my Zend project , and i dont need to have a Zend_http client to connect and retreive user info just tell me why it s better to process like u did and use Zend_Http instead of using FB, maybe i switch to your solution ;) thanks

  • Was just about to make this very adapter myself.. luckily I searched google first! I made some mods and am using it in my own work. I did, however, find one bug (semantic, probably wouldn't be a security problem), and made two changes that might just be a matter of personal preference than anything else: Bug: setAppId() has a parameter of $appId, but sets $this->_appId to $id (which will always be null). Changes: 1. Instead of referring to Zend_Auth_Adapter_Facebook to access class constants or other methods / properties "staticly", I prefer to refer to the current class as self::FOO instead of My_Class::FOO. Since I checkout the Zend library as an SVN external, I like to put my own Zend extensions in a different namespace.. such as in my case where I named the class M_Auth_Adapter_Facebook. 2. More of a personal preference than anything else, I've always found putting lots of parameters in any function / method very painful and hard to remember, so I just put one parameter in the construct ($config), and set the appId, secret, etc to use those, or otherwise default to the default app id that I have setup in my application.ini Thanks for posting this!

  • Thank you. It works very good.

  • Phil

    Couple of points... Always exit; after issuing a location header. Also, I'd be more inclined to let the controller handle the redirection, that's what it's meant to do. Use the information in the auth result to determine the appropriate action. Another one, provide any dependencies (eg Zend_Controller_Request) to the adapter, don't fetch them from the front controller instance. Have a look at Zend_Auth_Adapter_Http for some ideas.

  • How about if you have a local authentication? I tried to integrate this with my authentication but am having weird problems... Thanks

  • Arunmohan

    Thanks.. It works...

  • Hello! Nice work, but in our projects we use symlinks to default Zend Framework package and i wanna know how i can use your adapter from another exile folder on server (like "library/somename/auth/adapter")?

  • Mikhail= Honestly, I'd just rename the class to some other library. Maybe call it Krotscheck_Auth_Adapter_Facebook or Kittens_Auth_Adapter_Facebook or something similar, and then place it in the appropriate folder.

  • Salvatore

    I'm trying to use it but is giving me this errors Undefined index: access_token in /home/apps/domains/apps.radical.ie/public_html/anpost/anpost_app/library/Zend/Auth/Adapter/Facebook.php on line 161 Undefined property: stdClass::$id in /home/apps/domains/apps.radical.ie/public_html/anpost/anpost_app/library/Zend/Auth/Adapter/Facebook.php on line 165

  • Hey there, Salvatore! This particular article is actually pretty old, and I don't really support this code anymore. The two errors you found should be pretty easy to debug though. Good luck!

  • daniele

    Thanks! This code works fine for me :)

Leave a Reply