Michael Krotscheck’s insights, ideas, and inspirations about web technology, life, and the kitchen sink.

Zend_Auth_Adapter_Facebook

August 21st, 2010

Tags: , , ,

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);
		}
	}
}

1 Comment »

 

Featured Project: HP Print Studio

HP Print Studio Portfolio Image 3

HP Print Studio is an internet application that allows users to easily create professionally designed home printing projects customized with their own text and images, and it was the first project I completed at Resource Interactive, marking my transition from a lone-wolf to a team oriented developer. I won’t lie and say that the project was in any way easy- as the quote clearly demonstrates, I didn’t have the slightest clue on how the application would finally be assembled, though I really wanted to take a shot at it.

See all projects »