OpenID module gotchas

tnarg - October 6, 2005 - 18:24

I'm working on a module to support drupal logins using OpenID. For those of you not familiar, OpenID is a decentralized identity system. In OpenID, a users identity is keyed by a url that represents them. A "Consumer" is the site that the user wants to log in to, i.e. drupal, and the "Server" is the site that knows about the user.
The first problem I encountered is that OpenID users should not enter their password on the Consumer site. This should be a fairly obvious security concern. To side step this problem a created a separate block for OpenID login only that has one field for the user to enter their url. Does anyone see a problem with this?
I'm now stuck trying to figure out what to do about creating user accounts, user names, email address, etc. First of all, It doesn't seem that a URL is a valid drupal username. Ideally, when a user name is displayed for an OpenID user, it would appear as a hyperlinked URL. Second, it appears that an email address is required. I suppose that could be resolved by prompting the user for an email address when they first use their OpenID.
One thought I had was that I could create a separate database table the maps OpenID to user account. That could allow existing users to associate an openid with their account.

If anyone has any input, it would be greatly appreciated.

ugly trick

chx - October 6, 2005 - 18:46

as you might know http://username:password@my.site is a widely used url scheme (despite not valid, the logn part is only for FTP). Omit username:password and get http://@my.site . That is something that Drupal will understand as an external userid.

Caveat: Opera will give you a warning if you click on such a URL. Opera takes security very seriously.
--
Read my developer blog on Drupal4hu.

Thank You

javanaut - October 6, 2005 - 19:56

First off, thank you for working on this. I see OpenID as an important contribution that could someday take over Drupal's internal distributed auth system.

To your point, I thought the OpenID Server sent back a handle that you could use to construct a local account with. For example, if you loged in as "bloghost.com/users/brad123" then the server returned a parameter of "Brad".

When I first read the idea of representing users as URLs instead of "user" or "user@host", I was a bit curious how these identities would be integrated into conventional systems. I'm wondering if Drupal's auth mechanism will need to be expanded to accommodate this format.

The url is the handle

tnarg - October 6, 2005 - 20:35

The way OpenID is defined, it is intended that the URL be the handle. Otherwise, if something like "Brad" was returned, there would be no guarantee that it wouldn't already be used by someone else on the site.

I think that ideally, different authorization mechanisms would have there own tables that pointed into the global user table. This way, things like passwords and identifiers would be associated with a particular authorization mechanism. Then the user table would just contain a display name.

actually...

walkah - October 6, 2005 - 23:37

we can already make extensive use of the authmap table... yes, i agree that the user@server restriction needs to be lifted (anyone noticed the zcallbacks.module hacking that ldap_integration has had to do??

we may need to augment authmaps slightly, but it's exactly the base we want to build on.

--
James Walker :: Bryght Guy

What I have so far

tnarg - October 7, 2005 - 00:37

Here is a copy of what I have so far. Currently it just does the OpenID Identity verification and prints out some messages indicating whether it was successful or not. In particular, the "doXXX" methods of the DrupalActionHandler need to actually do something.

<?php
// $Id: openid.module,v 1.9 2005/04/25 16:45:26 unconed Exp $

/**
*  This module requires that you have version 0.0.8.X PHP-OpenID
*  library somewhere where php can find it.
*  The PHP-OpenID library is available at the following url:
*
*      <a href="http://videntity.org/openid/
" title="http://videntity.org/openid/
" rel="nofollow">http://videntity.org/openid/
</a> */
require_once( 'PHP-OpenID-0.0.8.3/openid/consumer.php' );


/**
* Implements hook_help
*/
function openid_help($section) {
  switch (
$section) {
  case
'admin/modules#description':
    return
t('Allows users to authenticate via OpenID');
  }
}

/**
* Return info for dist_auth usage
*/
function openid_info($field = 0) {
 
$info['name'] = 'OpenID';

  if (
$field) {
    return
$info[$field];
  }
  else {
    return
$info;
  }
}

/**
* Implement hook_auth to show in available dist_auth list
*/
function openid_auth($username, $password, $server){
  return;
}

/**
* Implementation of hook_block
* Provides the OpenID login form
*/
function openid_block($op='list', $delta=0) {
  global
$user;
 
// listing of blocks, such as on the admin/block page
 
if ($op == "list") {
   
$block[0]["info"] = t("OpenID Login");
    return
$block;
  } else {
    if (!
$user->uid) {
     
// set up the block
     
$block['subject'] = 'OpenID Login';

     
$content = "<div class=\"user-login-block\">\n";
     
$content .= form_textfield(t('OpenID'), 'identity_url', $edit['identity_u\rl'], 15, 64);
     
$content .= form_submit(t('Log in'));
     
$content .= "</div>\n";
     
$block['content'] = form($content, 'post', url('openid/login'));
      return
$block;
    }
  }
}


/**
* Menu entries
* openid/login: Handles form submit from openid_block
* openid/return: handles redirect return from identity server
*
*/
function openid_menu($may_cache) {
 
$items = array();

  if (
$may_cache) {
   
// OpenID Form submit
   
$items[] = array('path' => 'openid/login', 'title' => t('openid login'),
                    
'callback' => 'openid_login', 'type' => MENU_CALLBACK,
                    
'access' => TRUE);
   
$items[] = array('path' => 'openid/return', 'title' => t('openid return'),
                    
'callback' => 'openid_ret', 'type' => MENU_CALLBACK,
                    
'access' => TRUE);
  }

  return
$items;
}


/**
* Callback for the OpenID login form
*/
function openid_login() {
 
$edit = $_POST['edit'];
 
$op = $_POST['op'];

 
$identity_url = $edit['identity_url'];

 
$query = array_merge( $_GET, $_POST );
 
$consumer = new DrupalConsumer(new DrupalHTTPClient());
 
$handler = new DrupalActionHandler($query, $consumer);

 
$ret = $consumer->find_identity_info($identity_url);
  if( !
$ret ) {
   
printf('Unable to find openid server for identity url %s', $identity_url);
  } else {
   
// found identity server info
   
list( $identity_url, $server_id, $server_url ) = $ret;

   
$trust_root = url(NULL, NULL, NULL, TRUE);

   
// build url to application for use in creating return_to
   
$app_url = url('openid/return', NULL, NULL, TRUE);

   
// create return_to url from app_url
   
$return_to = $handler->createReturnTo($app_url, $identity_url);

   
// handle the request
   
$redirect_url = $consumer->handle_request(
                                             
$server_id, $server_url, $return_to, $trust_root);

   
// redirect the user-agent to the server
   
header( 'Location: ' . $redirect_url );
    exit;
  }
}


/**
* Callback for return_to url redirection. The Identity server will redirect
* back to this handler with the results of the authentication attempt.
*/
function openid_ret() {
 
$edit = $_POST['edit'];
 
$op = $_POST['op'];

 
$query = array_merge( $_GET, $_POST );
 
$consumer = new DrupalConsumer(new DrupalHTTPClient());
 
$handler = new DrupalActionHandler($query, $consumer);
 
$openid = $handler->getOpenID();
 
$req = new ConsumerRequest($openid, $query, 'GET');
 
$response = $consumer->handle_response($req);
 
$response->doAction($handler);
}


/**
* Our OpenIDConsumer subclass.  See openid.consumer.OpenIDConsumer
* for more more documentation.
*/
class DrupalConsumer extends OpenIDConsumer {

  function
verify_return_to( $return_to ) {
    if(
strpos($return_to, url('openid/return', NULL, NULL, TRUE)) != 0) {
      return
false;
    }
    return
true;
  }
};


/**
* A handler with application specific callback logic.
*/
class DrupalActionHandler extends ActionHandler {

  function
DrupalActionHandler($query, $consumer) {
   
$this->query = $query;
   
$this->consumer = $consumer;
  }

 
/* callbacks */
 
function doValidLogin($login) {
   
// here is where you would do what is necessary to log an openid "user"
    // user into your system.  We just print a message confirming the
    // valid login.
   
printf( '<b>Identity verified!</b> Thanks, ' .
           
'<a href="/%s">' .
           
'%s</a>', $this->query['open_id'], $this->query['open_id'] );
  }

  function
doInvalidLogin() {
    print
'OpenID verification failed!';
  }

  function
doUserCancelled() {
    print
'OpenID login Cancelled by user.';
  }

  function
doCheckAuthRequired($server_url, $return_to, $post_data) {
   
// do openid.mode=check_authentication call, and then change state
   
$response = $this->consumer->check_auth($server_url, $return_to,
                                           
$post_data, $this->getOpenID());
   
$response->doAction($this);
  }

  function
doErrorFromServer($message) {
    print
'Error from Identity Server: ' . $message;
  }

 
/* helpers */
 
function createReturnTo($base_url, $identity_url, $args=null) {
    if( !
is_array( $args ) ) {
     
$args = array();
    }
   
$args['open_id'] = $identity_url;
    return
oidUtil::append_args($base_url, $args);
  }

  function
getOpenID() {
   
// return the openid from the original form
   
return $this->query['open_id'];
  }
};


/**
* A wrapper around drupal_http_request presenting the
* expected interface to the OpenID library
*/
class DrupalHTTPClient extends HTTPClient {

  function
get( $url) {
   
// -->(final_url, data)
    // Fetch the content of url, following redirects, and return the
    // final url and page data as a tuple.  Return NULL on failure.

   
$redirs = 0;
    while (
$redirs < 10) {
     
$result = drupal_http_request($url, array(), 'GET', NULL, 0);

      switch (
$result->code) {
      case
200:
        return array(
$url, $result->data);
      case
301:
      case
302:
      case
307:
       
$url = $result->redirect_url;
       
$redirs += 1;
        break;
      default:
        return
NULL;
      }
    }
  }

  function
post( $url, $body) {
   
// -->(final_url, data)
    // Post the body string argument to url.
    // Reutrn the resulting final url and page data as a
    // tuple. Return NULL on failure.
   
$result = drupal_http_request($url, array(), 'POST', $body, 0);

    if(
$result->code != 200) {
      return
NULL;
    }
    return array(
$url, $result->data);
  }

 
// static
 
function getHTTPClient() {
    return new
DrupalHTTPClient();
  }

};

?>

Let's have a meeting

Boris Mann - October 6, 2005 - 22:29

We (Bryght) definitely want to support OpenID, perhaps as a secure replacement for Drupal auth, but in general as one of the main open distributed identity mechanisms.

You might want to check out the SXIP module that James created, as it deals with a lot of these same issues. James has been working with a bunch of Jabber stuff that might be related as well, and is in general an expert on those concepts of user/distributed auth stuff.

Perhaps we can have a meeting over Skype or IRC for everyone interested in identity stuff and Drupal? Use my contact form to get in touch. We should also put this on the agenda for the meetings in Amsterdam.

--
Please turn on the "story" type, so we can use it to have an archive of best practices, how tos, and configuration recipes.

I'm happy to help with the

alexmc - November 8, 2005 - 14:55

I'm happy to help with the OpenID implementation either in coding, testing or documentation. Just give me a shout when there is some kind of SIG.

It is rather hard to see who is working on this right now.

OpenID Module Add On

yuumei - August 28, 2006 - 14:54

Hi, I have been messing with the OpenID module and added a setting for assigning "default roles" to an OpenID registered user.
I have changed the following:

Add the roles to the user on signup (line 309):

<?php
    $user
= user_save('', array('name' => $_SESSION['openid'],
                               
'pass' => user_password(),
                               
'mail' => $query['edit']['email'],
                               
'init' => $_SESSION['openid'],
                               
'status' => 1,
                               
"authname_openid" => $_SESSION['openid'],
                               
'roles' => variable_get('openid_default_roles', DRUPAL_AUTHENTICATED_RID))); // <-- here
?>

Add a function to change the settings:
<?php
function openid_settings() {
 
// Default role settings:
 
$roles = user_roles(1);
 
$options = $roles;
 
$form['openid'] = array('#type' => 'fieldset', '#title' => 'OpenID settings');
 
$form['openid']['openid_default_roles'] = array('#type' => 'checkboxes', '#title' => t('Default roles'), '#default_value' => variable_get('openid_default_roles', ''), '#options' => $options, '#description' => t('Enter the default user roles for an OpenID user.'));

  return
system_settings_form('openid_settings', $form);
}
?>

Add the help text:
<?php
function openid_help($section) {
    switch (
$section) {
    case
'user/help#openid':
        return
t('Allows users to authenticate via OpenID');
    case
'admin/modules#description':
      return
t('Allows users to authenticate via OpenID');
    case
'admin/settings/openid':
      return
t('<p>Here, you can edit the defaults for the OpenID module</p>');

    }
}
?>

Add the menu options:
<?php
function openid_menu($may_cache) {
   
$items = array();

   
$items[] = array('path' => 'openid/get_email', 'title' => t('openid email form'),
                    
'callback' => 'openid_email_form', 'type' => MENU_CALLBACK,
                    
'access' => TRUE);
   
$items[] = array('path' => 'openid/login', 'title' => t('openid login'),
                    
'callback' => 'openid_login', 'type' => MENU_CALLBACK,
                    
'access' => TRUE);
   
$items[] = array('path' => 'openid/return', 'title' => t('openid return'),
                    
'callback' => 'openid_return', 'type' => MENU_CALLBACK,
                    
'access' => TRUE);
   
$items[] = array('path' => 'admin/settings/openid',
                    
'title' => t('openid'), 'callback' => 'openid_settings');

    return
$items;
}
?>

^^*

Cool beans so far Check out

shadyman@errora... - January 6, 2007 - 04:17

Cool beans so far

Check out http://iwantmyopenid.org/ - they have bounties going for OpenID support.

openid news

henadzi - July 11, 2008 - 08:31

Found OpenID News

I think links located there may be useful for openid beginners ...
---
MyDrupal

 
 

Drupal is a registered trademark of Dries Buytaert.