Simple Comments and OpenID (2/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.
[previous] [next] |
Created: July 31, 2008
Revised: July 31, 2008
URL: http://webreference.com/programming/perl/comments/openid/2.html


