Hello, please sign in or register
You are here: Home

Openid


This is my little script for consuming OpenID services. OpenID.net has some good documents for developers. I found this was easier to implement myself than downloading some of the libraries (which are very crufty) in essence here's roughly the process...

See demo - write an email for one of the following domains. Alternatively enter an Openid URI.

Implement

  1. Copy and paste the code into your dev environment: where you can access it through your browser.
  2. Write an email address belonging to any of the mentioned accounts in the first page
  3. ... keep 'clicking' the post button.

The PHP OpenID Code

Code so far...

<?php
/**
 * Brief introduction to testing a Google account login
 * 
 * @author Andrew Dodson
 * @since 31-01-2009
 * 
 */
session_start();

if( !empty( $_POST['CONSUME_URL'] ) ) {

	$post_url = $_POST['CONSUME_URL'];

	unset($_POST['CONSUME_URL']);

	
	$r = @file_get_contents( $post_url . '?' . http_build_query(array_filter($_POST)));
	
	// If this is not an XML or HTML response, extract items from the querystring
	if( strpos(ltrim($r),'<')===false && preg_match_all('/([a-z0-9\_]+)=([^&]+)/i',$r, $matches, PREG_SET_ORDER) )
		foreach($matches as $o)
			$res[$o[1]] = urldecode($o[2]);
}

/**
 * Supported Services
 */
$xrds_docs = array(
		'@gmail.com' 		=> 'https://www.google.com/accounts/o8/id', 
		'@googlemail.com'	=> 'https://www.google.com/accounts/o8/id',
		'@yahoo.co.uk'		=> 'http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds',
		'@yahoo.com'		=> 'http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds',
		'@aol.com'			=> 'http://openid.aol.com/{username}',// not an XRDS doc
		'@aol.co.uk'		=> 'http://openid.aol.com/{username}',// not an XRDS doc
	);


/**
 * DEFAULTS
 */
$_GET += array('step'=>0); // default step
$form_method = 'post';
$highlight = array();
$thispage = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; // For prepopulating returnurl and cancelurl

/**
 * STEPS
 * These steps are defined throughout the page
 */
switch($_GET['step']){
case 0:
	$ACTION = "Get the XRDS document based upon the users email domain";
	$r = "Please write an email belonging to one of the domains below";
	$res = $xrds_docs;
	$form = array(
			'email' => "",
		);
	break;
case 1:
	// Openid URL ?
	if( preg_match('#https?\:\/\/#i', $_POST['email'] ) ){
		// read it?
		$post_url = $_POST['email'];
		$r = strtr(file_get_contents($_POST['email']), array("\r"=>" ", "\n"=>" "));
		// carry on to step 2;
	}
	// Email
	else if( strpos($_POST['email'],'@') ){
		$ACTION = "Get the XRDS document - 
					this gets relative information about the openid connection\n\n
					(This should be a more sophisticated discovery process)";

		$form = array(
				'CONSUME_URL' => str_replace('{username}',substr($_POST['email'],0,strpos($_POST['email'],'@')), $xrds_docs[strstr($_POST['email'],'@')])
			);
		break;
	}
	// XRI @iname, =iname, =inumber
	else if( in_array( $_POST['email'][0], array('@','=') )
			&& array_key_exists('Location', $arr = get_headers("http://xri.net/".$_POST['email'], 1) ) ){
		// Get the contents of the new location
		$r = strtr(file_get_contents($arr['Location']), array("\r"=>" ", "\n"=>" "));
		// carry on to step 2;
	}
	else{
		$r = "Can't understand";
		break;
	}
	
	// If gets to hete, then the contents of $r is an HTML document.
	// Lets determine if there is an XRDS document to open... so many connections!
	// <meta http-equiv="X-XRDS-Location" content="http://linksafe.name/oid_xrds/@freeid*andrew" />
	// <meta http-equiv="X-YADIS-Location" content="http://linksafe.name/oid_xrds/@freeid*andrew" />
	if( ( strpos( $r, 'X-XRDS-Location' ) || strpos($r, 'X-YADIS-Location') ) 
		&& ($html_pattern = '((http-equiv\s*\=\s*[\'|"]X-(XRDS|YADIS)-Location[\'|"])|(content\s*\=\s*[\'|"](.*?)[\'|"]))')
		&& preg_match( $s = '#<meta\s+'.$html_pattern.'[^>]*?'.$html_pattern.'#', $r, $matches ) ){

		$form = array(
				'CONSUME_URL' => @$matches[10].@$matches[5]
			);
		break;
	}
	// carry on to step 2;
	$_GET['step'] = 2;

case 2:
	$ACTION = "An internal request between the servers for a shared identity key used for authenticating";

	/**
	 * DISCOVERY
	 */
	// XRDS|YARIS Discovery
	if(strpos($r, '<xrds') && preg_match_all('#<Service\s*(.*?)>(.*?)</Service>#is',$r,$matches,PREG_SET_ORDER)){ //XRDS Doc
		foreach($matches as $o){
			preg_match( '#priority\s*=\s*[\'"]?([0-9]+)[\'"]?#i', $o[1], $ma);
			preg_match_all('#<(type|uri|openid:delegate|localid)>(.*?)</\1>#i',$o[2],$arr_matches);
			$arr_match[(int)$ma[1]] = array_combine($arr_matches[2],$arr_matches[1]);
		}
		// Order by priority
		ksort($arr_match);
		// Get the first <service> tag settings
		foreach( $arr_match[key($arr_match)] as $val => $key){
			$key=strtolower($key);
			if($key==='type'){
				$a = array(
					'http://openid.net/signon/1.0' => 1.1,
					'http://openid.net/signon/1.1' => 1.1,
					'http://specs.openid.net/auth/2.0/signon' => 2,
					'http://specs.openid.net/auth/2.0/server' => 2
				);
				if(array_key_exists($val, $a))
					$version = $a[$val];
			}
			else if($key==='uri'){
				$form_redirect = $val;
			}
			else if($key==='openid:delegate'||$key==='localid'){
				$identity_path = $val;
			}
		}
		if ( $version === 2 && empty( $idendtity_param ) )
			$identity_path = 'http://specs.openid.net/auth/2.0/identifier_select'; // this is required by Yahoo

	}
	// HTML Discovery e.g. 
	else if( ($html_pattern = '((rel\s*\=\s*[\'|"]((openid2?.(delegate|server|provider|local_id)\s*){1,2})[\'|"])|(href\s*\=\s*([\'|"])([^\'"]+)))')
			&& preg_match_all('#<link\s+'.$html_pattern.'[^>]*?'.$html_pattern.'#', $r, $matches) )
	{
		// default identity is the one give but this may be overwritten by the identity if openid.deligate or openid2.local_id is present
		// Select Openid2 over the others
		$ar = array_filter( array_combine($matches[5],$matches[16]) + array_combine($matches[12],$matches[8]) );
		$form_redirect = ( array_key_exists('provider',$ar) ? $ar['provider'] : $ar['server'] );
		$identity_path = ( array_key_exists('local_id',$ar) 
								? $ar['local_id'] 
								: ( array_key_exists('delegate',$ar)
									? $ar['delegate'] 
									: $post_url ) );
		unset($ar);
	}

	if( empty($form_redirect) ){
		$r .= "\n\nCould not find the openid.server";
		break;
	}
	/**
	 * Obtain using cURL what is known as the MAC key for authenticating the response.
	 */
	$form_method	= 'get';
	$form = array(
			'openid.realm'		=> 'http://'.$_SERVER['HTTP_HOST'],
			'openid.ns'			=> 'http://specs.openid.net/auth/2.0',
			'openid.mode'		=> 'associate',
			'openid.assoc_type'	=> 'HMAC-SHA1',
			'openid.session_type'	=> ($version===1.1?'':'no-encryption'),
		);

	$r .= "\n\n---REQUEST--\n\n" . $form_redirect . '?' . http_build_query(array_filter($form)) . "\n\n---RESPONSE--\n\n";



	preg_match_all( '#([a-z\_]+):(.*)#m', $s = file_get_contents( $form_redirect . '?' . http_build_query(array_filter($form))), $parms);
	
	$r .= $s;

	$res = array_combine($parms[1],$parms[2]);
	
	$_SESSION['mac'] = base64_decode($res['mac_key']);
	$_SESSION['assoc_handle'] = $res['assoc_handle'];
	$_SESSION['endpoint'] 	= $form_redirect;

	/**
	 * Form specifics
	 */
	$form = array(
		'openid.ns'			=> 'http://specs.openid.net/auth/2.0',
		'openid.return_to'	=> $thispage . '?step=' . ($_GET['step']+1),
		'openid.mode'		=> 'checkid_setup', // OR checkid_immediate
		'openid.claimed_id'	=> $identity_path, //http://specs.openid.net/auth/2.0/identifier_select
		'openid.identity'	=> $identity_path, //http://specs.openid.net/auth/2.0/identifier_select
		'openid.realm'		=> 'http://'.$_SERVER['HTTP_HOST'],
		'openid.trust_root'	=> 'http://'.$_SERVER['HTTP_HOST'],
		// Authenticate that this is a response from google
		'openid.assoc_handle'	=> $res['assoc_handle'],
		// Obtain user email
		'openid.ns.ext1'  	=> "http://openid.net/srv/ax/1.0",
		'openid.ext1.mode'	=>'fetch_request',
		'openid.ext1.type.email' =>'http://axschema.org/contact/email',
		'openid.ext1.type.nickname'	=> "http://schema.openid.net/namePerson/friendly",
		'openid.ext1.type.fullname'	=> "http://schema.openid.net/namePerson",
		'openid.ext1.if_available'	=> "nickname,fullname,email",
		'openid.ns.sreg'		=> "http://openid.net/sreg/1.0",
		'openid.sreg.optional' =>'nickname,fullname,email',
	);

	/**
	 * Presentation
	 */
	$highlight[] = $form_redirect; // presentation
	break;
case 3:
	$ACTION = "Authenticating the information - using the openid.sig value";
	/**
	 * SIGNED
	 * The signed string is built from all the parameters (mentioned in $signed.
	 */
	$res = $_GET;

	if($_SESSION['assoc_handle']===$res['openid_assoc_handle']){

		$signed = '';
		foreach( explode(",", $res['openid_signed']) as $field)
			$signed .= ''. $field .':'. $res['openid_'.str_replace('.','_',$field)] . "\n";

		/**
		 * Is Valid?
		 */
		if( $res['openid_sig'] === base64_encode(hash_hmac('sha1',$signed,$_SESSION['mac'], true))){
			$r = "\n\n-- VALIDATED REPSONSE: Signatures match --\n\n";
		}
		else 
			$r = "\n\n-- INVALID REPSONSE--\n\n";
	}
	else{
		$r = "\n\n-- Assoc Handles Do NOT match ( USING DUMB MODE - One more step )--\n\n";

		$form_redirect = $_SESSION['endpoint'];
		$form = array(
			'openid.mode'	=> 'check_authentication',
			'openid.assoc_handle' => $res['openid_assoc_handle'],
			'openid.sig' 	=> $res['openid_sig'],
			'openid.signed'	=> $res['openid_signed'],
		);

		foreach( explode(",", $res['openid_signed']) as $field)
			$form['openid.'.$field] = $res['openid_'.str_replace('.','_',$field)];
	}
			
	// User Is logged in
	$r .= $_SERVER['QUERY_STRING'];

	break;
}

/**
 * Highlight
 */
 
$arr_highlight = array();
foreach((array)@$highlight as $str)
	@$arr_highlight[$str] = '<b>'.$str.'</b>';

/**/
?>
<html>
<style>
	*{font-size:10px;font-family:verdana;}
	input{width:300px;}
	pre{background:#eee;}
</style>
<body>
<h1>Step <?=$_GET['step']. ': '. $ACTION?></h1>
<?php
if(isset($r)){
	print "<h2>Response</h2>";
	if(isset($redirect_url))
		print "Redirect: <a href='$redirect_url' target='_blank'>$redirect_url</a>";
	print "<p>-RAW-</p><pre>" . strtr(htmlspecialchars($r), $arr_highlight) . "</pre>";
}

if(isset($res))
	print "<p>-ARRAY-</p><pre>".print_r($res, true)."</pre>";

?>
<a href='<?=$thispage?>'>reset</a>
<h2>Request: <?=$desc?> <br />METHOD: <?=$form_method?><br />ACTION: <?=$form_redirect?></h2>
<?php if(!empty($form)){?>
<form method='<?=$form_method?>' action='<?=isset($form_redirect)?$form_redirect:$thispage.'?step='.($_GET['step']+1)?>'>
<table>
<?php
foreach($form as $k=>$o){
	print '<tr><td>'.$k.'</td><td><input name="'.$k.'" value="'.$o.'"/></td></tr>';
}
?>
</table>
<input type='submit' />
</form>
<?php } ?>
</body>
</html> 


Resources

 

Comments

drew
Created 04/02/09
Thanks
This saved me an awful lot of time. I'm surprised I couldn't find a detailed tutorial anywhere else as everyone just says use a library. The part this helped with the most is validating the signature correctly as everything has to be encoded correctl...
Created 03/12/10
check_authentication
check_authentication is not working with Yahoo. However working with Google and AOL. is there any problem with Yahoo ?
Created 05/04/12
oakleysunglasses
“I canada goose outlet think it doesn’t swarovski jewelry bode very
Created 21/01/16
linpingping
The michael--kors.org.uk next ralph lauren outlet day patriots jersey Mrs
Created 01/03/16
clibin
Tyrion 5c cases watched her ray ban sunglasses read. His timber...
Created 18/04/16
chenyan
Bond grinned. pandora-bracciali.it "We bcbgmax.in.net only chaussure...
Created 15/06/16
hxy1220
hxy1220 ray ban wayfarer rolex watches,rolex watches,swiss watches,watches for men,watches for women,omega watches,replica ...
Created 20/12/16
Title*
Comment

Prove you are not a robot

To prove you are not a robot, please type in the six character code you see in the picture below
Security confirmation codeI can't see this!
Contact
Name*
Email never shown*
Home Page

Author

Andrew Dodson
Since:Feb 2007

Comment | flag

Categories

Bookmark and Share