I recently had to add an authentication system to an application of mine.
Another login, another password checking, another… Boooring. Given the nature of the application I decided that this time I could cut the mess down and go only with the «Use your Google account» “one click” login. Why not? Lot of people have an google account already and it’s OpenID!
The usual Google documentation page is this one. The good news is that if you follow this guide you probably will not need to read that whole page :) Later on you should integrate this very document with the Google one.
The first thing to know is that you are going to write an OpenID client/consumer. OpenID 2 protocol is indeed the system behind the whole client/server scenario from your application (the client) and the Google accounting system (the server). I have to assume that you know what that means (at an high level, at least). The examples here are in PHP and below I will provide some sample code for you to play with.
OK; so here are the relevant points:
- This one is theoretical, but it’s how Google deployed the OpenID protocol on top of his already existing authentication system. Normally, in a “pure” OpenID environment, during the login process the user will give you his/her openid identifier (an URL); using that identifier your program will discover (via the Yadis protocol) which is the corresponding server party to talk to for the authentication process. In the Google scenario you don’t know which is the user’s OpenID identifier (the user, of course, is not even aware that there’s something like “openid” involved in the way s/he logs in). You have NOT to ask her for her “google account”; you have to make him press a button or follow a link to “Login with your Google account”. But you DO know who you have to ask for protocol informations and not have to infer it from the user’s OpenID (technically, a discovery process is needed anyway). Upon a succesfull login you WILL have the user’s OpenID identifier; it’s something like a generated string, not choosen by the user and crafted by Google itself.
- You need a recent OpenID library that supports OpenID 2.0 XRI and Yadis discovery. This excludes (as for now) the Zend Framework OpenID component (it does support OpenID 2.0, but not the discovery part in the version Google uses). Too bad. You then need to go straight to the JanRain library
- Don’t bother thinking about Simple Registration (SREG): Google does support the more advanced (and useful) Attribute Exchange (AX) extension only
- Speaking of profile data, there is nothing much you can get: email, name and surname. I noticed that unless you specify each one as mandatory (within the AX request), the user is not even asked – by Google – to give them to you. There’s probably somethng I misunderstood, but this is it.
On your side, the login process will consist of two distint phases: one that initiates the login process the and one that finalizes it.
Normally you point your “Login with Google…” link to the script that implements the initiation phase (let’s say “login.php”). The finalize script (let’s say “finalize.php”) will be called by the Google server as per protocol. There is a lot more to know about the OpenID process, of course, but the library should hide the gory details for you.
The login script will immediately and trasparently forward the user to the real Google account login page. The finalize script will have the logic that checks if the authentication has been succesfull or not and act consequently.
I’m about to show you some code, but keep in mind that this is NOT valid PHP code; I want you to understand what’s going on rather than being able to just copy/paste. To further limit clutter I did not even add basic error checking. Please check the JanRain library examples for full fledged (and working) code.
So, for the login process:
# Requires the needed JanRain scripts
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";
require_once "Auth/OpenID/AX.php";
# Setup the library (leaved blank on purpose).
# Will also create the $store variable used below
...
$consumer = new Auth_OpenID_Consumer($store);
$auth_request = $consumer->begin("https://www.google.com/accounts/o8/id");
# Create attribute request object (this is where you prepare the Attribute Exchange request)
$attribute = array();
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/contact/email', 2, true, 'email');
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/first', 1, false, 'firstname');
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/last', 1, false, 'lastname');
# Create AX fetch request
$ax = new Auth_OpenID_AX_FetchRequest();
# Add attributes to AX fetch request
foreach($attribute as $attr){
$ax->add($attr);
}
$auth_request->addExtension($ax);
$return_to = sprintf("http://%s:%s%s/finalize.php", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], dirname($_SERVER['PHP_SELF']));
$trust_root = sprintf("http://%s:%s%s/", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], dirname($_SERVER['PHP_SELF']));
$form_html = $auth_request->htmlMarkup($trust_root, $return_to, false, array('id' => 'openid_message'));
# This will actually redirect to Google
print $form_html;
This is what I have in my finalize.php script:
# Requires the needed JanRain scripts
require_once "Auth/OpenID/Consumer.php";
require_once "Auth/OpenID/FileStore.php";
require_once "Auth/OpenID/AX.php";
# Setup the library (leaved blank on purpose).
# Will also create the $store variable used below
...
$consumer = new Auth_OpenID_Consumer($store);
$return_to = sprintf("http://%s:%s%s/finalize.php", $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'], dirname($_SERVER['PHP_SELF']));
$response = $consumer->complete($return_to);
$msg = '';
switch($response->status) {
case Auth_OpenID_CANCEL:
# This means the authentication was cancelled by the user
$msg = 'Verification cancelled.';
break;
case Auth_OpenID_FAILURE:
$msg = "OpenID authentication failed: " . $response->message;
break;
case Auth_OpenID_SUCCESS:
# "$openid" is the user openid given by Google
$openid = $response->getDisplayIdentifier();
$ax_resp = Auth_OpenID_AX_FetchResponse::fromSuccessResponse($response);
# Here you could save the user data in session for further references...
...
# Redirect the user to her dashboard
header('Location: /account');
exit();
}