OpenID and LRDD

In my last article I wrote about the differences between user discovery and provider discovery. In this article, I will explain how both of these discovery flows can be easily be done using the “Link-base Resource Descriptor Discovery” (LRDD) pattern. A resource descriptor is, for the purposes of OpenID discovery, an XRD document describing meta-data about a resource, which can either be a user’s OpenID (i.e., identified by a user identifier) or an OpenID provider (i.e., identified by a provider identifier).

Provider Discovery

Provider Discovery starts with an identifier of a provider, and tries to obtain the OP endpoint for that provider. Let’s say that the provider identifier is the host name of the OpenID provider, e.g. “example.com”, and that that provider has an OP endpoint at http://openid.example.com/op.

The location of the OP endpoint is just a piece of meta data about the provider. The meta data for a host can be found in the host-meta document for that host. The host-meta standard is still somewhat in flux, but likely this means that the meta-data for example.com can be found at a location that looks something like this: http://example.com/.well-known/host-meta.

The format of this host-meta file is XRD – which means that the XRD document we get by fetching http://example.com/.well-known/host-meta is the OpenID provider’s XRD document. In it, there will be a bunch of meta-data about the provider (for example, the location of its favicon, etc.), and it could also have the location of the OP endpoint:


<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
     xmlns:hm='http://host-meta.net/ns/1.0'>

    <hm:Host>example.com</hm:Host>

    <Link rel='http://specs.openid.net/auth/2.1/provider'
          href='http://openid.example.com/op'>
</XRD>

This means that the OpenID Provider’s endpoint for example.com is at http://openid.example.com/op.

That was easy!

User Discovery

User Discovery starts with an OpenID (the user identifier), say http://example.com/openid/bob, and tries to obtain the OP endpoint for that user (which we’ll assume is still http://openid.example.com/op). Again, the location of the user’s OP endpoint is just a piece of meta-data about the OpenID in question. LRDD says that there are three ways to find the location of the meta-data for a HTTP URI.

First is a Link-header in an HTTP response when accessing the OpenID itself:


> GET /openid/bob HTTP/1.1
> Host: example.com
> Accept: */*

< HTTP/1.1 200 OK
< Content-Length: 157
< Link: <http://example.com/user?openid=http%3a%2f%2fexample.com%2fopenid%2fbob>;
<             rel=describedby; type=application/xrd+xml
< ...

The RP could now fetch the user’s XRD from http://example.com/user?openid=http%3a%2f%2fexample.com%2fopenid%2fbob and get something that looks like this:


<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>

    <Subject>http://example.com/openid/bob</Subject>

    <Link rel='http://specs.openid.net/auth/2.1/provider'
          href='http://openid.example.com/op'>
</XRD>

And again, we’re done. But I said there were three ways to get to the same XRD document – so far we’ve only looked at Link-headers. The second way is to look for a link element in the HTML returned from http://example.com/openid/bob that would similarly point to the XRD at http://example.com/user?openid=http%3a%2f%2fexample.com%2fopenid%2fbob.

The third way involves host-meta again. Let’s say that Bob’s OpenID doesn’t serve anything:


> GET /id/bob HTTP/1.1
> Host: example.com
> Accept: */*

< HTTP/1.1 404 Not Found

We could still find Bob’s XRD document, by fetching a host-meta from the host that is part of Bob’s OpenID – in this case that host is example.com. The host-meta for that host is at http://example.com/.well-known/host-meta, and we’ve already seen parts of it above. Here it is again, with some more parts of it revealed:


<?xml version='1.0' encoding='UTF-8'?>
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'
     xmlns:hm='http://host-meta.net/ns/1.0'>

    <hm:Host>example.com</hm:Host>

    <Link rel='http://specs.openid.net/auth/2.1/provider'
          href='http://openid.example.com/op'>
    <Link rel='describedby'
          template='http://example.com/user?openid={%uri}'>
</XRD>

Because the RP knows that it is performing discovery on the user identifier http://example.com/openid/bob, not the provider identifier example.com, it skips past the Link that points directly to the OP endpoint, and instead looks for a URI template of Rel-Type “describedby”. This means that the URI obtained by “plugging” the user identifier http://example.com/openid/bob into the URI template points to an XRD document that “describes” (i.e. has meta-data about) http://example.com/openid/bob. The URI we obtain this way is, again, http://example.com/user?openid=http%3a%2f%2fexample.com%2fopenid%2fbob, so the RP would go and fetch Bob’s XRD from that location. We’ve shown above what that XRD looks like.

To summarize: to obtain the OP endpoint for a user identifier (i.e., OpenID), the RP needs to find the user’s XRD document and then look up the OP endpoint in that document. There are three ways to find the user’s XRD document: (1) by following a Link-header in the HTTP response from accessing the OpenID itself, (2) by following a link element in the HTML we get by accessing the OpenID itself, and (3) by applying the OpenID URI to a URI template in the host-meta of the host that is part of the user’s OpenID URI.

What order should an RP try these in? I would argue that an RP should try the host-meta approach first, and the reason is security-related: Let’s assume for a moment that we try the link-element-embedded-in-HTML approach first. This means that users who have personal home pages at their OpenID URLs can point to their OpenID providers, and can override whatever their IT staff is setting up through host-meta. It is fairly easy to convince users through social engineering to add a bit of HTML to their page, as long as it comes with the promise of some cool functionality.

So let’s say Bob works at Example Corp, and his OpenID is http://example.com/openid/bob. Example Corp has set up a host-meta at http://example.com/.well-known/host-meta that says that all users’ OpenID endpoint is at http://openid.example.com/op. Bob now falls for a scam that promises fame and fortune if only he adds the following line of HTLM to his home page at http://example.com/openid/bob:


<link rel="describedby" uri="http://evil.com/openid?user=bob">

At this point, evil.com can hijack Bob’s identity on the Web, because RPs would check the embedded link element first, and would miss the fact that example.com’s host meta is actually pointing somewhere else.

Note that this doesn’t take away control from the user. A site like blogger.com can decide not have a host-meta (or at least not put openid-related information in there), and would therefore delegate to each user the power to name their own OpenID provider.

One thought on “OpenID and LRDD

  1. Hi Dirk,
    Good write-up.
    Several Points:
    <Host> is not there in XRD schema, I believe. It is .
    <Rel> in User XRD should represent the relationship and not the service type URI.
    <URI> there should point to non-information resource URI of the OP.
    I agree that we should use signed site-meta as the first option.
    I am not sure if we need to keep all three options for OpenID 2.1.
    If we can just decide on one, it will be kinder for the developers, IMHO.
    Nat Sakimura
    http://www.sakimura.org/en/

Comments are closed.