OpenID logo Poidsy A PHP OpenID Consumer

Background Information

Poidsy is designed to be both easy to use and easy to integrate with existing systems. This document explains the developer's interface to Poidsy, and works through modifying a basic authentication script to accept OpenID logins with Poidsy.

Requirements

Before you begin using Poidsy, you need to check that your environment is supported. The easiest way to do this is to download a release of Poidsy, and view the included test.php file in your web browser. This will tell you about any problems it has encountered, and what will/will not work as a result.

How it works

The bulk of the work done by Poidsy is done by the processor.php script. This is the file that you will be including into your login page.

The processor is designed to be invoked in response to a form submission. At present (version 0.1), it requires that $_POST['openid_url'] is set to the user-supplied identifier.

Once the processor has finished authenticating (or rejecting) the user's identifier, it will supply the details in a session variable. Thus, to actually make use of Poidsy's results, you will need to call session_start() on your login page (if you're not already).

While Poidsy is authenticating an identifier, the user may be redirected between the login page and their identity provider a number of times. Whenever the user is redirected to the login page during this process, the openid.mode parameter will be present.

Adding OpenID support to an existing login form

This example will walk through adding Poidsy to an existing login form. The system we'll be modifying currently deals with usernames and passwords. It is assumed that the Poidsy files are extracted to a poidsy directory in the same directory as the login file.

The example code that we'll be modifying is included below. For the sake of brevity, the specific logic of finding users, checking passwords, and actually logging them in is abstracted away.

<?PHP if (isset($_POST['user']) && isset($_POST['pass'])) { if (passwordMatches($_POST['user'], hashPassword($_POST['pass']))) { setUser($_POST['user']); redirectAndExit(); } else { define('ERROR', 'Invalid username/password combination'); } } if (defined('ERROR')) { echo '<div class="error">ERROR: ', htmlentities(ERROR), '</div>'; } ?> <p>Enter your details below to login:</p> <form action="login.php" method="post"> <label>Username: <input type="text" name="user"></label> <label>Password: <input type="password" name="pass"></label> <input type="submit" value="Login"> </form>

Step 1: Adding a new form

The first step is to provide users with a second form to enter their OpenID identifier in. It's good practice to include a little OpenID logo in the input field, so users can instantly see that it's for an OpenID identifier. Poidsy handily comes with such a logo.

The following code is added below the existing form:

<p>Alternatively, login using an OpenID identifier:</p> <form action="login.php" method="post"> <input type="text" name="openid_url" style="background-image: url('poidsy/openid.gif') no-repeat; padding-left: 20px;"> <input type="submit" value="Login"> </form>

Step 2: Starting a session

As described above, Poidsy uses sessions to track data and return its results. As our login form currently doesn't use sessions, we need to add a call to session_start(); before we'll be using Poidsy or its results.

The session start code therefore needs to be added before the main if statement.

<?PHP session_start(); if (isset($_POST['user']) && isset($_POST['pass'])) {

Step 3: Including the processor

The next step is to include Poidsy's processor when there's processing to be done. This is either when the user has just submitted the OpenID form, or when they've been redirected back from their provider during the authentication process.

We prepend the following conditions to the start of the if statement to include the processor when neccessary. Note that PHP translates the openid.mode argument to openid_mode.

session_start(); if (isset($_POST['openid_url']) || isset($_REQUEST['openid_mode'])) { // OpenID login attempt require('poidsy/processor.php'); } else if (isset($_POST['user']) && isset($_POST['pass'])) {

Step 4: Handling errors

Just like normal login attempts, OpenID authentications can fail for a variety of reasons (such as the identifier not being a valid OpenID endpoint, or the provide refusing to authenticate the client), so we need to handle errors and display them to the user.

If an error is encountered, Poidsy will have set the $_SESSION['openid']['error'] variable with a description of the problem. We simply check for this and pass it on to the user:

require('poidsy/processor.php'); } else if (isset($_SESSION['openid']['error'])) { // Failed OpenID login attempt define('ERROR', $_SESSION['openid']['error']); unset($_SESSION['openid']['error']); } else if (isset($_POST['user']) && isset($_POST['pass'])) {

Step 5: Handling success

The final thing to do is to handle the case when a user has successfully authenticated themselves using an OpenID identifier. In a typical environment this will be a two-step process: creating a new account for the user if they haven't previously logged in, and then logging the user in to their account.

The following code adds another branch to our if statement:

unset($_SESSION['openid']['error']); } else if (isset($_SESSION['openid']['validated']) && $_SESSION['openid']['validated']) { // OpenID authentication successful if (!isAccount($_SESSION['openid']['identity'])) { // Create an account if they need one, using a fake // password hash so users can't login normally createAccount($_SESSION['openid']['identity'], 'invalid'): } setUser($_SESSION['openid']['identity']); unset($_SESSION['openid']['validated']); redirectAndExit(); } else if (isset($_POST['user']) && isset($_POST['pass'])) {

Summary

Hopefully this document has given you a sense of how to use Poidsy. It is designed to work transparently with your existing applications, and as demonstrated above can be integrated in five simple steps.

Note that, of course, how you implement Poidsy very much depends on your existing system and your requirements. If you are going to switch to using just OpenID, there is obviously no need to deal with password hashes as was done in this example, and you could in fact not use a backend database at all for your users — just rely on Poidsy to populate the $_SESSION['openid'] array.

Feedback

If you're using Poidsy (or are trying to but are having problems) then I'd love to hear from you. My contact details can be found on my personal website.

Constant reference

You can define a set of constants to control the behaviour of Poidsy. These should be defined before the processor is included.

From Poidsy 0.1:

OPENID_URL
The user-supplied OpenID URL to validate. This should only be specified for the initial request (not any subsequent redirects where openid.mode is set). If not provided, Poidsy will use the value of $_POST['openid_url'] if it exists

From Poidsy 0.2:

OPENID_THROTTLE_NUM
The maximum number of requests to allow before throttling a user. Default value is 3.
OPENID_THROTTLE_GAP
The number of seconds that have to ellapse between requests before the user's counter is reset. Default value is 30.

From Poidsy 0.3:

OPENID_SREG_REQUEST
A comma-separated list of fields to request from the user via the simple registration extension. Use of this constant implies that the user will be required to manually input the data if their provider doesn't supply it.
OPENID_SREG_OPTIONAL
A comma-separated list of fields that the user's provider may provide, but aren't required by the application.
OPENID_SREG_POLICY
An URL for a document that describes how the data requested using SREG is going to be used.
OPENID_NOKEYMANAGER
Stops Poidsy using its key manager, forcing it to use dumb mode instead. The value of the constant is irrelevant, only whether it is defined or not.
OPENID_IMMEDIATE
Forces Poidsy to try an openid_immediate request first. If not defined, Poidsy will send an openid_setup request, which allows the identity provider to interact with the user.
As of 0.4, if this is set to true, Poidsy will error if the provider requires interaction, otherwise Poidsy will send a setup request. Previous behaviour always errored.
OPENID_TRUSTROOT
The trust root to send to providers. Used to tell users what site they're logging into. Should be a fully qualified URL that encompasses the URL of the login script. Defaults to the current URL.
As of 0.5, this may be truncated in order to ensure it is above the return_to URL.
OPENID_ALLOWUSER
If defined, allows usernames (and passwords) in identity URLs. These are stripped by default to prevent users using the same identity with multiple (bogus, unused) usernames.
OPENID_ALLOWQUERY
If defined, allows queries (the part of the URL after the "?" character) in identity URLs. These are stripped by default to prevent users from using the same identity with multiple (unused) query strings.

Error codes

As of Poidsy 0.3, each error has an associated errorcode that defines the type of error. These error codes are:

autherror
Error when trying to POST data to authenticate a request in dumb mode, probably because the provider went down. Removed as of Poidsy 0.4.
cancelled
The user or their identity provider cancelled the request for some reason
diffid
The provider validated a different identity to the one Poidsy requested. Indicates that the provider is probably broken.
diffreturnto
The provider gave a different return_to URL to the one the user arrived at
discovery
There was an error during discovery (site down, unsupported protocol, etc)
noauth
The signature of the provider's response didn't match the response. This could mean that something is broken, or that someone is trying one of a variety of attacks.
noimmediate
OPENID_IMMEDIATE was defined but the request couldn't be completed immediately
nonce
The nonce used by Poidsy didn't match what it was expecting. This normally means the user tried to go to an expired page, or someone was trying to perform a replay attack.
notvalid
The identity isn't valid (no openid.server link found)
perror
The provider returned an error at some stage
throttled
The user is being throttled by Poidsy. See the description of the OPENID_THROTTLE constants above.