Getting Started with OpenID and PHP

June 4, 2008

Tutorials

Out Of Memory

Something odd happened the other day. I Webbed over to a site that I visit on an irregular basis, signed in with my username and password, and hit the submit button. After a few seconds of clicking and grinding, the machinery informed me that my password had failed verification. Puzzled, I tried a few more times but, encountering zero success, stamped my heel, uttered a few creative curses and moved on to more productive work. It was only later, after submitting to various security questions and recovering my original password, that I realized my goof-up: I’d used a password belonging to another site by mistake!

If this story sounds familiar to you, that’s because it’s the story of today’s Web: too many sites, too many usernames, and not enough cranial capacity to file them all accurately. But there’s good news at hand: OpenID, a free, open-source framework for “single sign-on” across different Web sites and applications. The even better news? There already exist a bunch of PHP widgets that allow developers to easily integrate OpenID into a PHP application, and this article is going to show you how to use them. So what are you waiting for? Flip the page, and let’s get going!

Digital Me

Before diving into the code, let’s spend a few minutes answering a basic question: what’s this OpenID thing anyway and how does it work?

According to the official OpenID Web site OpenID is

“a free and easy way to use a single digital identity across the Internet”. Fundamentally, an OpenID is a customized URL, chosen by you as your online identity and registered with an OpenID service provider. Whenever an external site needs to verify your identity for login purposes, you supply this URL instead of your username; the site then contacts your OpenID service provider for authentication.

What’s the benefit? Simple: because your OpenID is stored with your OpenID service provider and any site can contact this provider to authenticate you, there’s no need to create multiple accounts or remember multiple usernames and passwords for different sites; all you need is a single OpenID. This assumes, of course, that the external site supports the OpenID framework; adoption of this is gradually increasing, and the OpenID Web site has some interesting information about various large organizations that have begun using the framework.

Typically, there are two parties to an OpenID transaction: Consumer and Provider. A Provider is like a registrar: it allows users to create and register OpenID URLs and manage their OpenID accounts, and it also authenticates the user to Consumers on demand. A Consumer (also sometimes called a Relying Party) is an OpenID-enabled Web site.

The OpenID framework is completely open-source and any Web site can become a Consumer or a Provider of OpenIDs without incurring any costs on licensing fees. As a result, there are already a large number of OpenID Providers on the Web, and a growing number of Web sites have begun allowing users to sign in to their services using an OpenID.

What happens in an OpenID transaction? Well, when a user tries logging into a Consumer site with an OpenID, the Consumer contacts the Provider to verify the user’s credentials before allowing him or her access. The user may be redirected to the Provider and asked to sign in to his or her account with the Provider using a password; once this is successfully done, the Provider automatically redirects the user back to the Consumer site, which now treats the user as verified and grants him or her the necessary access. A shared key, known to both parties and protected with strong encryption, is used throughout to maintain the integrity of the transaction and avoid “spoofing”.

If you’re new to OpenID, the information above should be sufficient to explain the basic concepts and ensure that you can follow the material that comes next; however, if you want/need a more detailed description, I’d recommend that you take a look at the OpenID developer site, at http://openid.net/developers/ and the OpenID 1.1 specification.

Assembling The Pieces

Now that you’ve (hopefully) understood the basics of how the OpenID framework works, let’s turn to a more pressing question: where does PHP fit it? Well, there are a number of OpenID libraries written for PHP, and designed to help developers quickly add OpenID support to their Web application. This tutorial discusses two of them:

  1. The PHP OpenID Library, maintained by JanRain Inc. (JanRain Inc. also operates MyOpenID.com, a popular provider of OpenID identities). This is a stable implementation for both client and server ends of an OpenID connection, and it’s used in most of the examples in this tutorial.
  2. The Authentication::OpenID_Consumer PEAR package, proposed by Pádraic Brady. It should be noted that this package is still in proposal stage at the time of writing and should be considered alpha-state code; it’s used briefly in this tutorial to illustrate an alternative implementation to the JanRain library.

In case you don’t already have them, you’ll also need to download and install the following PEAR packages:

You can install these packages manually, or using the PEAR installer, as below:

shell> pear install Crypt_HMAC2

In order to try out the examples in this tutorial, you’ll also need your own OpenID. Get one from http://www.myopenid.com/ or any other OpenID service provider (and remember that you can also use it on any OpenID-enabled Web site!). If you use the MyOpenID service, your OpenID will probably be in the form http://yourname.myopenid.com, and will be generated for you free of charge.

Once you’ve got all the pieces together, you’re ready to go. But before you flip the page, I should make one rather important disclaimer: I’m not an expert on OpenID and this tutorial isn’t intended to be an exhaustive reference to OpenID integration (specifications and client libraries change too quickly to even attempt such a lofty goal). Rather, it’s intended as a general introduction for PHP developers who are new to OpenID, to give them a broad idea of how PHP/OpenID integration works and increase their comfort level with the technology. For this reason, I’ve kept the code listings fairly simple; remember that you can always find more complex code examples in the documentation supplied with the client libraries mentioned previously.

With that caveat out of the way, let’s get started!

First Steps

The first thing you’ll need, if you’re going to begin accepting OpenIDs on your Web site, is a sign-in form. Here’s the code:

<form method="post">
      Sign in with your OpenID: <br/>
      <input type="text" name="id" size="30" />
      <br />
      <input type="submit" name="submit" value="Log In" />
</form>

Here’s what the form looks like:

You’ll notice that this sign-in form doesn’t include a field for the user’s password. This is because, under the OpenID framework, authentication is handled by the OpenID Provider; all the user needs to access a Consumer site is his OpenID.

When the user submits this form with his or her OpenID, the form processor needs to locate the OpenID Provider and redirect to the Provider for authentication. The PHP OpenID Library can take care of this for you; consider the next listing, which wraps the form above in a conditional test and adds in the code that runs on form submission (I’m assuming here that your site – the Consumer site – is located at http://consumer.example.com, but feel free to change this to http://localhost for testing purposes):

<?php
if (!isset($_POST['submit'])) {
?>  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
      Sign in with your OpenID: <br/>
      <input type="text" name="id" size="30" />
      <br />
      <input type="submit" name="submit" value="Log In" />
    </form>
  </body>
</html>
<?php
} else {
  // check for form input
  if (trim($_POST['id'] == '')) {
    die("ERROR: Please enter a valid OpenID.");    
  }
  
  // include files
  require_once "Auth/OpenID/Consumer.php";
  require_once "Auth/OpenID/FileStore.php";
  
  // start session (needed for YADIS)
  session_start();
  
  // create file storage area for OpenID data
  $store = new Auth_OpenID_FileStore('./oid_store');
  
  // create OpenID consumer
  $consumer = new Auth_OpenID_Consumer($store);
  
  // begin sign-in process
  // create an authentication request to the OpenID provider
  $auth = $consumer->begin($_POST['id']);
  if (!$auth) {
    die("ERROR: Please enter a valid OpenID.");
  }
  
  // redirect to OpenID provider for authentication
  $url = $auth->redirectURL('http://consumer.example.com/', 'http://consumer.example.com/oid_return.php');
  header('Location: ' . $url);
}
?>    

As explained previously, authentication with OpenID is a two-step process: first, the Consumer contacts the Provider with the user’s OpenID and then, the Provider performs authentication and returns control to the Consumer to complete the authentication process.

The script above performs the first step of this process. Once the form is submitted and validated, a new PHP session is started and two object instances are created: Auth_OpenID_FileStore and Auth_OpenID_Consumer. The Auth_OpenID_FileStore represents a disk location that the PHP OpenID Library will use to store data relating to the current authentication attempt; the object constructor should be passed the name of the directory to use (it will attempt to create this directory if it doesn’t already exist). The Auth_OpenID_Consumer object represents an OpenID Consumer, and its constructor is passed the Auth_OpenID_FileStore object generated in the previous step.

To begin the sign-in process, the script calls the Auth_OpenID_Consumer object’s begin() method, passing it the user’s OpenID. The return value of this method is an Auth_OpenID_AuthRequest object, which represents an authentication request. This object’s redirectURL() method is then invoked with two arguments: the URL used to identify your site to the OpenID Provider, and the URL to which the OpenID Provider should return control post-authentication. The return value of redirectURL() is a URL string, which is used to redirect the user’s browser to the OpenID Provider’s Web site.

At this point, control transfers to the OpenID Provider, which may require the user to authenticate himself or herself using a password. Once this process is completed, the OpenID Provider redirects the user’s browser back to the URL passed as second argument to the redirectURL() method – in this example, the script oid_return.php on the Consumer’s domain. Typically, the OpenID Provider will also attach various bits of information to the query string as GET parameters; these are used by the Consumer to complete the authentication process.

Let’s now look at what the oid_return.php script does:

<?php
// include files
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";

// start session (needed for YADIS)
session_start();

// create file storage area for OpenID data
$store = new Auth_OpenID_FileStore('./oid_store');

// create OpenID consumer
// read response from OpenID provider
$consumer = new Auth_OpenID_Consumer($store);
$response = $consumer->complete('http://consumer.example.com/oid_return.php');

// set session variable depending on authentication result
if ($response->status == Auth_OpenID_SUCCESS) {
  $_SESSION['OPENID_AUTH'] = true;
} else {
  $_SESSION['OPENID_AUTH'] = false;    
}

// redirect to restricted application page
header('Location: restricted.php');  
?>

The first half of this script is similar to what you’ve previously seen: it initializes the storage area and creates a new Auth_OpenID_Consumer object. It then calls the object’s complete() method, passing it the URL string that the OpenID Provider redirected to; the return value of complete() is an Auth_OpenID_ConsumerResponse object, which represents the response of the OpenID Provider to the authentication request. Four response codes are possible: Auth_OpenID_SUCCESS, which indicates that authentication was successful; Auth_OpenID_FAILURE, which indicates that authentication failed; Auth_OpenID_CANCEL, which indicates that the authentication request was cancelled by the user; and Auth_OpenID_SETUP_NEEDED, which only appears if the OpenID server was asked to authenticate non-interactively but was unable to do so.

In our example above, once authentication is complete, a session variable named $_SESSION['OPENID_AUTH'] is initialized with a Boolean value indicating whether authentication was successful or not. It’s now easy to use this session variable for page-level authentication, by looking for it in every restricted page of the site, and only allowing the user access if this variable is set to Boolean true. Here’s a simple example of how this could be implemented in one such restricted page (restricted.php):

<?php
// check authentication status
session_start();
if (!isset($_SESSION['OPENID_AUTH']) || $_SESSION['OPENID_AUTH'] !== true) {
  die ('You are not permitted to access this page! Please log in again.');
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <h2>Restricted Page</h2>
    <p>You will only see this page if your OpenID has been successfully authenticated.</p>
  </body>
</html>

If a user attempts to access such a restricted page without successfully authenticating his or her OpenID, $_SESSION['OPENID_AUTH'] will either not exist or be false and the user will simply see an error message. It’s only after successful authentication with the OpenID Provider, that $_SESSION['OPENID_AUTH'] will become true and the user will be able to view the content of the restricted page.

Keeping It Simple

When you sign up for an OpenID, chances are that you’ll have been asked for optional profile information, including your name, email address, language and country of residence. The OpenID specification includes a provision for Consumers to retrieve this profile information from a Provider during the authentication process. This so-called Simple Registration extension is fully supported in the PHP OpenID Library, and the following revision of the previous example illustrates its usage:

<?php
if (!isset($_POST['submit'])) {
?>  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
      Sign in with your OpenID: <br/>
      <input type="text" name="id" size="30" />
      <br />
      <input type="submit" name="submit" value="Log In" />
    </form>
  </body>
</html>
<?php
} else {
  // check for form input
  if (trim($_POST['id'] == '')) {
    die("ERROR: Please enter a valid OpenID.");    
  }
  
  // include files
  require_once "Auth/OpenID/Consumer.php";
  require_once "Auth/OpenID/FileStore.php";
  require_once "Auth/OpenID/SReg.php";
  
  // start session (needed for YADIS)
  session_start();
  
  // create file storage area for OpenID data
  $store = new Auth_OpenID_FileStore('./oid_store');
  
  // create OpenID consumer
  $consumer = new Auth_OpenID_Consumer($store);
    
  // begin sign-in process
  // create an authentication request to the OpenID provider
  $auth = $consumer->begin($_POST['id']);
  if (!$auth) {
    die("ERROR: Please enter a valid OpenID.");
  }
  
  // create request for registration data
  $sreg = Auth_OpenID_SRegRequest::build(array('email', 'fullname', 'dob', 'language'), array('nickname'));
  if (!$sreg) {
    die("ERROR: Unable to build Simple Registration request");
  }
  $auth->addExtension($sreg);
  
  // redirect to OpenID provider for authentication
  $url = $auth->redirectURL('http://consumer.example.com/', 'http://consumer.example.com/oid_return.php');
  header('Location: ' . $url);
}
?>    

In a Simple Registration request, a Consumer can ask for any or all of 8 pieces of information: nickname, email address, full name, date of birth in YYYY-MM-DD format, gender, postcode, country of residence, language and time zone. Each of these is represented by a key: for example, ‘dob’ for date of birth, or ‘email’ for email address.

To create a Simple Registration request using the PHP OpenID Library, call the Auth_OpenID_SRegRequest class’ static build() method with two arrays as argument: the first array lists required keys while the second lists optional keys. In the example above, the user’s full name, date of birth and language are required, and the user’s nickname is optional.

Assuming authentication succeeds, the profile attributes requested, if available with the OpenID Provider, are returned to the Consumer with the other query string parameters. They can then be retrieved as an associative array, by initializing an instance of the Auth_OpenID_SRegResponse class with the response packet and then calling the instance’s contents() method, as illustrated below:

<?php
// include files
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";
require_once "Auth/OpenID/SReg.php";

// start session (needed for YADIS)
session_start();

// create file storage area for OpenID data
$store = new Auth_OpenID_FileStore('./oid_store');

// create OpenID consumer
// read response from OpenID provider
$consumer = new Auth_OpenID_Consumer($store);
$response = $consumer->complete('http://consumer.example.com/oid_return.php');

// set session variable depending on authentication result
if ($response->status == Auth_OpenID_SUCCESS) {
  $_SESSION['OPENID_AUTH'] = true;
  
  // get registration information
  $sreg = new Auth_OpenID_SRegResponse();
  $obj = $sreg->fromSuccessResponse($response);
  $data = $obj->contents(); 

  // do something with the registration information
  // ...

} else {
  $_SESSION['OPENID_AUTH'] = false;    
}

// redirect to restricted application page
header('Location: restricted.php');  
?>

It’s now easy to use this profile information within the Consumer site’s workflow – for example, to automatically register the user ‘s account using his name, or to send an email to his email address. To illustrate, consider this enhancement of the previous script, which uses the email address retrieved from the OpenID Provider to check if the user already has an account on the Consumer’s system. If the answer is yes, a personalized welcome message is printed with the user’s email address; if not, a new user registration form is generated, with the fields pre-filled with data retrieved from the user’s profile. Here’s the code:

<?php
// include files
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";
require_once "Auth/OpenID/SReg.php";

// start session (needed for YADIS)
session_start();

// create file storage area for OpenID data
$store = new Auth_OpenID_FileStore('./oid_store');

// create OpenID consumer
// read response from OpenID provider
$consumer = new Auth_OpenID_Consumer($store);
$response = $consumer->complete('http://consumer.example.com/oid_return.php');

// set session variable depending on authentication result
if ($response->status == Auth_OpenID_SUCCESS) {
  $_SESSION['OPENID_AUTH'] = true;
  
  // get registration information
  $sreg = new Auth_OpenID_SRegResponse();
  $obj = $sreg->fromSuccessResponse($response);
  $data = $obj->contents();

  if (isset($data['email'])) {
    // if email address is available
    // check if the user already has an account on the system
    
    // open database connection
    $conn = mysql_connect('localhost', 'user', 'pass') or die('ERROR: Cannot connect to server');
    mysql_select_db('test') or die('ERROR: Could not select database');

    // execute query    
    $result = mysql_query("SELECT DISTINCT COUNT(*) FROM users WHERE email = '" . $data['email'] . "'") or die('ERROR: Could not execute query');
    
    $row = mysql_fetch_array($result);
    if ($row[0] == 1) {
      // if yes, display personalized welcome message
      $newUser = false;
      echo 'Hello and welcome back, ' . $data['email'];
      exit();
    } else {
      // if no, assume this is a new user
      $newUser = true;
    }
        
    // close connection
    mysql_free_result($result);    
    mysql_close($conn);
  } else {
    // if email address is not available
    // assume it's a new user
    $newUser = true;
  }
} else {
  $_SESSION['OPENID_AUTH'] = false;    
  die ('You are not permitted to access this page! Please log in again.');
}

// if user doesn't have an account, 
// or if email address is not available in profile info
// assume this is a new user registration
// display a registration form pre-filled with profile information
if ($newUser == true) {
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <h2>Create New Account</h2>
    <form method="post" action="register.php">
      Name: <br/>
      <input type="text" name="name" value="<?php echo @$data['fullname']; ?>" />
      <p />
      Email address: <br/>
      <input type="text" name="email" value="<?php echo @$data['email']; ?>" />
      <p />
      Date of birth (YYYY-MM-DD): <br/>
      <input type="text" name="dob" value="<?php echo @$data['dob']; ?>" />
      <p />
      <input type="submit" name="submit" value="Register" />
    </form>
  </body>
</html>
<?php  
}
?>

Here’s what a user with an account on the system would see after signing in with an OpenID:

And here’s what a new user would see after signing in with an OpenID:

A Question Of Storage

The previous examples have all used PHP OpenID Library’s file storage class for local storage of OpenID data. If this is not to your taste, you can also store OpenID data in a MySQL, PostgreSQL or SQLite database, by replacing the Auth_OpenID_FileStore object with Auth_OpenID_MySQLStore, Auth_OpenID_PostgreSQLStore or Auth_OpenID_SQLiteStore objects respectively.

The following example illustrates, using a MySQL database for storage:

<?php
if (!isset($_POST['submit'])) {
?>  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
      Sign in with your OpenID: <br/>
      <input type="text" name="id" size="30" />
      <br />
      <input type="submit" name="submit" value="Log In" />
    </form>
  </body>
</html>
<?php
} else {
  // check for form input
  if (trim($_POST['id'] == '')) {
    die("ERROR: Please enter a valid OpenID.");    
  }
  
  // include files
  require_once "Auth/OpenID/Consumer.php";
  require_once "Auth/OpenID/MySQLStore.php";
  require_once 'DB.php';
  
  // start session (needed for YADIS)
  session_start();
  
  // create PEAR DB DSN for MySQL
  $dsn = 'mysql://user:pass@localhost/openid';
  $options = array(
      'debug'       => 2,
      'portability' => DB_PORTABILITY_ALL,
  );
 
  // open connection 
  $db =& DB::connect($dsn, $options);
  if (PEAR::isError($db)) {
      die($db->getMessage());
  }
  
  // create MySQL database storage area for OpenID data
  $store = new Auth_OpenID_MySQLStore($db);
  $store->createTables();
  
  // create OpenID consumer
  $consumer = new Auth_OpenID_Consumer($store);
  
  // begin sign-in process
  // create an authentication request to the OpenID provider
  $auth = $consumer->begin($_POST['id']);
  if (!$auth) {
    die("ERROR: Please enter a valid OpenID.");
  }
  
  // redirect to OpenID provider for authentication
  $url = $auth->redirectURL('http://consumer.example.com/', 'http://consumer.example.com/oid_return.php');
  header('Location: ' . $url);
}
?>    

To use a MySQL database for storage, initialize an Auth_OpenID_MySQLStore object and pass the object constructor a PEAR DB connection handle to the database. Calling the object’s createTables() method takes care of initializing the necessary database tables, and the remainder of the script proceeds as before. When processing the data returned by the OpenID Provider in the return script oid_return.php, remember to again use Auth_OpenID_MySQLStore instead of Auth_OpenID_FileStore.

Incidentally, if you wish to use a custom database abstraction library instead of PEAR DB, you can do this by subclassing the Auth_OpenID_DatabaseConnection class and using it with your custom abstraction toolkit. Similarly, if you’d prefer to use a storage mechanism other than files or SQL databases, you can use the Auth_OpenID_OpenIDStore class as the basis for your custom store. For more information on this, look in the documentation for the JanRain PHP OpenID Library.

An Alternative Approach

An alternative OpenID Consumer implementation is that provided by the Authentication::OpenID_Consumer PEAR package, currently in proposal status. At the time of writing, this is alpha-quality code, but it’s interesting to see it in action nevertheless (note that you might need to set PHP’s error reporting level to ignore warnings and notices thrown by the package; this should be fixed in the final release). Consider the next listing, which is equivalent to the first example in this tutorial:

<?php
error_reporting(E_ERROR);
if (!isset($_POST['submit'])) {
?>  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title></title>
  </head>
  <body>
    <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
      Sign in with your OpenID: <br/>
      <input type="text" name="id" size="30" />
      <br />
      <input type="submit" name="submit" value="Log In" />
    </form>
  </body>
</html>
<?php
} else {
  // check for form input
  if (trim($_POST['id'] == '')) {
    die("ERROR: Please enter a valid OpenID.");    
  }
  
  // include files
  require_once 'OpenID/Consumer.php';
  require_once 'OpenID/Store/File.php';
  
  // start session
  session_start();

  // create store  
  $store = new OpenID_Store_File('./oid_store');
  
  // create consumer
  $consumer = new OpenID_Consumer($store);
  
  // begin sign-in process
  // create an authentication request to the OpenID provider
  $auth = $consumer->start($_POST['id']);
  if (!$auth) {
    die("ERROR: Please enter a valid OpenID.");
  }

  // redirect to OpenID provider for authentication
  $auth->redirect('http://consumer.example.com/oid_return.php', 'http://consumer.example.com/');
}
?>    

Apart from the differences in notation, this is remarkably similar to what you saw in previous examples: the script creates a file store, initializes a Consumer object with the store, and then calls the Consumer object’s start() method with the user-supplied OpenID to begin the authentication process with the OpenID Provider. The resulting Authorisation object’s redirect() method, which accepts both the return URL and the site identifier, then takes care of redirecting the user’s browser to the OpenID Provider’s Web site.

Once the user completes authentication, the OpenID Provider returns control to the Consumer script oid_return.php:

<?php
error_reporting(E_ERROR);
// include files
require_once 'OpenID/Consumer.php';
require_once 'OpenID/Store/File.php';

// start session
session_start();

// create store  
$store = new OpenID_Store_File('./oid_store');

// create consumer
$consumer = new OpenID_Consumer($store);

// set session variable depending on authentication result
if (isset($_GET)) {
    $response = $consumer->finish($_GET);
    $result = $response->getResult();
    if ($result == 'success') {
      $_SESSION['OPENID_AUTH'] = true;
    } else {
      $_SESSION['OPENID_AUTH'] = false;    
    }
}

// redirect to restricted application page
header('Location: restricted.php');  
?>

Here, the Consumer object’s finish() method is used to complete the authentication process. The result of the finish() method is a Response object, representing the response send by the OpenID Provider to the Consumer’s authentication request. This object’s getResult() method can be used to test the result of the authentication process, and take appropriate action depending on whether the result is successful or not.

Service With A Smile

Now that you’ve seen two different implementations of an OpenID Consumer, let’s turn our attention to the Provider end of the connection. First, it should be noted that the JanRain PHP OpenID Library discussed earlier in this article also includes a full-fledged OpenID Provider implementation, which you can use to custom-code your own OpenID server. An example server is included in the package archive to get you started.

In most cases, however, you can get away without coding your own OpenID Provider, especially if your needs are simple. There are a number of open-source packages that allow you to set up and manage an OpenID server “out of the box”; here’s a quick list:

And that’s about it for this article. Over the last few pages, I gave you a crash course in OpenID, explaining what it was and how it worked. I then coerced you into installing some PHP-based OpenID libraries, and showed you how you could use them to add OpenID support to your Web application. I also explained the Simple Registration extension to OpenID, showing you how it could be used to retrieve a user’s profile from an OpenID Provider and inject that data into your Web application. Finally, I demonstrated an alternative, PEAR-based implementation for OpenID Consumers, and pointed you to some easy-to-install-and-use scripts for setting up your own OpenID server.

If you’re interested in learning more about PHP and OpenID, also consider the following resources:

Have fun, and happy coding!

Copyright Melonfire, 2007. All rights reserved.

9 Responses to “Getting Started with OpenID and PHP”

  1. bubastix0 Says:

    Hey , great article !
    i was trying to implement it , but when i evaluate the line :
    if ($response->status == Auth_OpenID_SUCCESS)

    the object seems to be null , maybe its a change on some of the packages …
    you think that i could validate the user using another method?
    Many Thanks!

  2. raghuirukulla Says:

    Though the concept of OpenID is good not many user’s know the full potential of this service. There are hundreds and thousands of services which accepts openID for login and shorten the process of registration. http://www.ekoob.com/10-cool-sites-to-get-your-own-openid-4058/ this article has clearly mentioned the benefits and resources to get a oepnID.

  3. tatva13 Says:

    i am using openid 2.0.i made simple application in php using code given by u.i include all openid libs.but when i try to login with my any openid,i get response "failure" with a message "Server denied check_authentication" in oid_return.php.

    thanks for help in adavance…

  4. pkiula Says:

    Sorry, you mention JanRain and that experimental PEAR class. Your examples have this line:

    require(‘Auth/…’);

    Which ones is this? If it is JanRain’s library, then I don’t see their stuff "included" in the code before? Where is the "Auth" file located, isn’t that a PEAR thing, and if so, is that NOT the JanRain library? Your entire tutorial in the beginning is based on this Auth thing. How to get it to work?

  5. vvaswani Says:

    Alihan:

    Thanks for the comment. I think security is something that would need to be enforced by your application, and you would probably also need to trust that the OpenID provider has the necessary security systems in place.

    Take your analogy a bit further: if we take Yahoo as an example, Yahoo usernames are equally public (every time you send an email from Yahoo, the recipient knows your Yahoo username). However in general (most) users trust Yahoo with their data and assume that Yahoo is holding their passwords securely. You would need to extend the same level of trust to whoever holds your OpenID, IMO.

    farrelley:

    I take your point, but my own take on this is that OpenID is still fairly new and not many casual Web users even know that it exists. The big OpenID supporters probably also need to do a little more in terms of educating users about the benefits of OpenID (single user name being the key benefit). Keep the faith: adoption has been increasing gradually and will continue to do so until it reaches a tipping point.

  6. farrelley Says:

    The adoption rate for OpenId is very slow. When I first came out I thought it would be awesome but many sites are reluctant to move to an OpenID strategy. Don’t get me wrong there are many popular sites that are using it! I just don’t know if it’s worth spending time to get it up and running. Right now OpenID doesn’t carry that mentality that says "Oh yeah, I’ll comment because I can use my OpenID." It’s not stopping people from doing things yet. What’s everyone else think?

  7. _____anonymous_____ Says:

    OpenID is greate world wide application gor websites. And thank you for this article. But there is a question : How secure is it?

    I think it can’t be used in very secure sites. There is always a chance to get the password. And it’s easy to know someones openID because it’s public…

    And a handicap : when you’ll get the password, you’ll own all accounts for the user in internet…

  8. admin Says:

    Hi erangalp!

    Not, it’s not really odd at all. DevZone accepts articles from PHP developers world wide, many of whom don’t work with Zend Framework. This article was not written from a Zend Framework perspective. (Although I would love to have an article about implementing OpenId in a Zend Framework project…wanna write it?) :)

    But you are correct that Zend Framework does have an OpenId implementation, here is the link to it.
    Zend_OpenId

    Thanks for the comment.

    =C=

  9. erangalp Says:

    Seems odd an article on OpenID here doesn’t even mention it. You should check it out.