Simple Comments and OpenID (2/5) | WebReference

Simple Comments and OpenID (2/5)

To page 1current pageTo page 3To page 4To page 5
[previous] [next]

Simple Comments and OpenID

OpenID Identifiers

OpenID Identifiers--or the identification string the end user claims is his or her own unique identifier--can, at least as of OpenID 2.0, take one of two forms. They can be either a standard Web URL, or an XRI. Fortunately, the two can be easily distinguished, since XRIs always begin with one of a series of unique characters.

XRI Identifiers

In my previous article, I provided the basics of XRIs and won't duplicate that discussion here. For now, the important thing to realize is that the result of XRI resolution is an XRDS document; and within that XRDS document is all the necessary information to proceed with OpenID authentication.

The bigger question now is: "How do you resolve an XRI?" The detailed answer can be found in the XRI Resolution specifications - all 131 pages of it. Fortunately, there's a shortcut method that makes the resolution somewhat trivial; specifically, we can use a proxy service to perform the resolution for us. That process is also described in the XRI Resolution specs, and is a more manageable 5 pages. If you're interested, refer to Chapter 11 of the XRI Resolution specs.

As of this writing, Simple Comments uses the proxy service at http://xri.net to perform XRI resolutions. In brief, you would simply add the user's XRI onto the http://xri.net prefix, add the necessary parameters to tell the proxy exactly what you want to look up (i.e., do you want the full XRDS that belongs to the identifier? Or are you only interested in a particular service described in the XRDS?), and then GET the resulting URL exactly as you would any other URL. For example, if my XRI were =web*dragle then I could get the default response to my XRI by just hitting http://xri.net/=web*dragle. And if I wanted to get the XRDS file directly, I would use http://xri.net/=web*dragle?_xrd_r=application/xrds+xml.

A few coding rules must be observed when setting up such an HTTP-based XRI (called an HXRI in the XRI literature), and you can review these in Chapter 11 of the specs (link above). Here's how I translated those rules into Perl code for use in Simple Comments:

#------------------------------------------------------
# get_xrds_document - Attempts to resolve an XRI via 
#   the xri.net proxy. 
#------------------------------------------------------
sub get_xrds_document {
   my $self = shift;
   # Build up the HXRI, per XRI Resolution Specs
   my $hxri = URI->new('http://xri.net/' . 
                         $self->user_supplied_id_normalized)->canonical;
   # Note: separate subparameter values (if you want 
   # them) with %3B (the percent encoding for a semicolon)
   my $resolution_parameter = '_xrd_r=application/xrds+xml';
   # Check for a query component
   if ($hxri =~ /(\?.*)$/) {
      my $query = $1;
      $query = '?' . $query if ($query =~ /^\?+$/);
      $query =~ s/(_xrd_(r|m|t))/join('', map {sprintf("%%%02X", ord($_))} split('', $1))/ge;
      $query .= '&' . $resolution_parameter;
      $hxri =~ s/\?.*$/$query/;
   }
   else {
      # add the query
      $hxri .= '?' . $resolution_parameter;
   }
   # Final decoding before get
   $hxri =~ s/\%/\%25/g;
   
   return get_url($self->ua, $hxri);
}

In brief, we create the initial URL as we described above, then add the _xrd_r to tell the resolver what we want returned (i.e., you can think of this parameter as forcing a specific context onto the resolution process itself). We must then add the parameter to the URL, taking care to not overwrite any existing parameters that may already be included on the original URL. We also percent encode any existing _xrd_r parameters that may already be in the URL, so we don't accidentally override them with our own _xrd_r addition. Finally, we percent encode any included percent signs in our resulting URL. On the receiving side, any XRI compliant proxy will know how to perform each of these steps in reverse, so they can end up with the same XRI for resolution that we had in the first place. The resulting document from the proxy should be an XRDS.

URL Identifiers

If the identifier is not an XRI, then it is, by definition, a URL. That URL, however, can also (potentially) retrieve an XRDS document; via a process formerly known as Yadis and is now officially included as part of the XRI Resolution specifications (see Chapter 6, specifically). Yadis discovery uses Content Negotiation, in which we attempt to retrieve a specific type of document by including it in an ACCEPT header that we send to the target server. Or, in other words, we send a GET request to the target URL telling it that we prefer an XRDS document in return:

Accept: application/xrds+xml

In response to this, the target server will send us either the XRDS document itself; a pointer to the XRDS document (that pointer is sent in the form of an X-XRDS-Location, either as an actual HTTP header or as a META element in the head of the returned document), or, if the server doesn't support this type of content negotiation, a plain HTML document (whatever document would be returned by the URL identifier provided). We now have an XRDS to work with, know where the XRDS is (and can therefore proceed to retrieve it), or a plain HTML document. If we have an XRDS, then we should be able to find within it the data we need to locate the appropriate OpenID Provider to verify the end user's claim. Otherwise, we should be able to parse the HTML document we have to discover the OpenID meta links that we need to find out where the OpenID Provider is located which can verify this identifier. Whew! In Perl speak, this is what I came up with:

#------------------------------------------------------
# get_yadis_document - Attempts to perform YADIS 
#   discovery an a provided identifier
#------------------------------------------------------
sub get_yadis_document {
   my $self      = shift;
   my $yadis_url = undef;
   # Perform a head request
   my $res = $self->ua->head($self->user_supplied_id_normalized);
   return $res unless ($res->is_success);
   # Save off the new normalized id after redirects
   $self->user_supplied_id_normalized($res->request->uri->canonical);
   if ($res->header('X-XRDS-Location')) {
      $yadis_url = URI->new($res->header('X-XRDS-Location'))->canonical;
   }
   # If we didn't get a URL, we have to try a separate get
   unless ($yadis_url) {
      $res = $self->ua->get($self->user_supplied_id_normalized, 
                            'Accept' => 'application/xrds+xml');
      return $res unless ($res->is_success);
      if ($res->header('Content-type') =~ m|^application/xrds\+xml$|i) {
         return $res;
      }
      else {
         if ($res->header('X-XRDS-Location')) {
            $yadis_url = URI->new($res->header('X-XRDS-Location'))->canonical;
         }
      }
   }
   # Perform the final get of the XML document
   $res = $self->ua->get($yadis_url, 'Accept' => 'application/xrds+xml')
      if ($yadis_url && ($yadis_url =~ m|^https?://|));
   return $res;
}

Of course, I'm leaving out a few details here for the sake of brevity. For those dying to get the straight scoop, I again refer you to the OpenID Specifications.

At this point we've gotten far enough to determine who the OpenID Provider is (either that, or know that any further processing of this identifier will be futile and we can stop now). Read on to discover how we can now use Diffie-Hellman key exchange to shorten the overall authentication process.


To page 1current pageTo page 3To page 4To page 5
[previous] [next]

Created: July 31, 2008
Revised: July 31, 2008

URL: http://webreference.com/programming/perl/comments/openid/2.html