In my previous article on OpenID 2.0, I mentioned the new Attribute Exchange extension. To me this is one of the more interesting benefits of moving to OpenID 2.0, so it deserves a more in depth look.
As mentioned previously, the extension is a way of transferring information about the user between the OpenID provider and relying party.
Why use Attribute Exchange instead of FOAF or Microformats?
Before deciding to use OpenID for information exchange, it is worth looking at whether it is necessary at all.
There are existing solutions for transferring user data such as FOAF and the hCard microformat. As the relying party already has the user’s identity URL, it’d be trivial to discover a FOAF file or hCard content there. That said, there are some disadvantages to this method:
- Any information published in this way is available to everyone. This might be fine for some classes of information (your name, a picture, your favourite colour), but not for others (your email address, phone number or similar).
- The same information is provided to all parties. Perhaps you want to provide different email addresses to work related sites.
- The RP needs to make an additional request for the data. If we can provide the information as part of the OpenID authentication request, it will reduce the number of round trips that need to be made. In turn, this should reduce the amount of time it takes to log the user in.
Why use Attribute Exchange instead of the Simple Registration extension?
There already exists an OpenID extension for transferring user details to the RP, in the form of the Simple Registration extension. It has already been used in the field, and works with OpenID 1.1 too.
One big downside of SREG is that it only supports a limited number of attributes. If you need to transfer more attributes, you basically have two choices:
- use some other extension to transfer the remaining attributes
- make up some new attribute names to send with SREG and hope for the best.
The main problem with (2) is that there is no way to tell between your own extensions to SREG and someone else’s which will likely create interoperability problems if when an attribute name conflict occurs. So this solution is not a good idea outside of closed systems. This leaves (1), for which Attribute Exchange is a decent choice.
What can I do with Attribute Exchange?
There are two primary operations that can be performed with the extension:
- fetch some attribute values
- store some attribute values
Both operations are performed as part of an OpenID authentication request. Among other things, this allows:
- The OP to ask the user which requested attributes to send
- If the OP has not stored values for the requested attributes, it could get the user to enter them in and store them for next time.
- The OP could use a predefined policy to decide what to send the RP. One possibility would be to generate one-time email addresses specific to a particular RP.
- For store requests, the OP can ask the user to confirm that they want to store the attributes.
An attribute fetch request is a normal authentication request with a few additional fields:
- openid.ax.mode: this needs to be set to “fetch_request”
- openid.ax.required: a comma separated list of attribute aliases that the RP needs (note that this does not guarantee that the OP will return those attributes).
- openid.ax.if_available: a comma separated list of attribute aliases that the RP would like returned if available.
- openid.ax.type.alias: for each requested attribute alias, the URI identifying the attribute type
- openid.ax.count.alias: the number of values the RP would like for the attribute.
- openid.ax.update_url: a URL to send updates to (will be discussed later).
The use of URIs to identify attributes makes it trivial to define new attributes without conflicting with other people (and as with XML namespaces, the attribute aliases are arbitrary). However, the extension is only useful if the OP and RP can agree on attribute types. To help with this, there is a collection of community defined attribute types at axschema.org.
As an example, imagine a web log that uses OpenID to authenticate comment posts. Rather than just printing the OpenID URL for the commenter, it could use attribute exchange to request their name, email, website and hackergotchi. The authentication request might contain the following additional fields:
openid.ns.ax=http://openid.net/srv/ax/1.0 openid.ax.mode=fetch_request openid.ax.required=name,hackergotchi openid.ax.if_available=email,web openid.ax.type.name=http://axschema.org/namePerson openid.ax.type.email=http://axschema.org/contact/email openid.ax.type.hackergotchi=http://axschema.org/media/image/default openid.ax.type.web=http://axschema.org/contact/web/default
In the successful authentication response, the following fields will be included (assuming the OP supports the extension):
- openid.ax.mode: must be “fetch_response”
- openid.ax.type.alias: specify the type URI for each attribute being returned.
- openid.ax.count.alias: the number of values being returned for the given attribute alias (defaults to 1).
- openid.ax.value.alias: the value for the given attribute alias, if no corresponding openid.ax.count.alias field was sent.
- openid.ax.value.alias.n: the nth value for the given attribute alias, if a corresponding openid.ax.count.alias field was sent. The first attribute value is sent with n = 1.
- openid.ax.update_url: to be discussed later.
For the web log example given above, the response might look like:
openid.ns.ax=http://openid.net/srv/ax/1.0 openid.ax.mode=fetch_response openid.ax.type.name=http://axschema.org/namePerson openid.ax.type.email=http://axschema.org/contact/email openid.ax.type.hackergotchi=http://axschema.org/media/image/default openid.ax.value.name=John Doe firstname.lastname@example.org openid.ax.count.hackergotchi=0
In this response, we can see the following:
- The user has provided their name and email
- They have not provided any information about their web site. Either the OP does not support the attribute or the user has declined to provide it.
- The use has explicitly stated that they have no hackergotchi (i.e. it is a zero-valued attribute).
Using the Attribute Exchange fetch request, it is possible to outsource management of pretty much all the user’s profile information to the OP. That said, the user will still need to update their profile data occasionally. Telling them to go to their OP to change things and then log in again is not particularly user friendly though.
Using the store request, the RP can let the user update their profile on site and then transfer the changes back to the OP. Like the fetch request, a store request is performed as part of an OpenID authentication request. The additional request fields are pretty much identical to a store response, except that openid.ax.mode is set to “store_request”.
In the positive authentication response, the RP can see whether the data was successfully stored by checking the openid.ax.mode response field. If the data was stored, then it will be set to “store_response_success”. If the data was not stored it will be set to “store_response_failure” and an error message may be found in openid.ax.error.
Asynchronous Attribute Updates
One downside of the Simple Registration extension is that it only transferred user details on login. This means that it is only possible to get updates to attribute values by asking the user to log in again. The Attribute Exchange extension provides a way to solve this problem in the form of the openid.ax.update_url request field.
When a “fetch_request” is issued with the openid.ax.update_url field set, a compliant OP will record the following:
- the claimed ID and local ID from the authentication request
- the list of requested attributes
- the update_url value (after verifying that it matches the openid.realm value of the authentication request).
The OP will then include openid.ax.update_url in the authentication response as an acknowledgement to the RP. When any of the given attributes are updated the OP will send an unsolicited positive authentication response to the given update URL. This will effectively be the same as the original authentication response (i.e. for the same claimed ID and local ID), but with new values for the changed attributes.
As there is no mention of unsolicited authentication responses in the main OpenID authentication specification, it is worth looking at what checking the RP should do. This includes:
- Is this OP still authoritative for the claimed ID? This is checked by performing discovery on the claimed ID and verifying that it results in the same server URL and local ID as given in the response.
- Did the message come from the OP? As with a standard response, there should be a signature for the fields. Since the OP does not know what association to use for the signature, a new private association will be used. By issuing a “check_authentication” request to the OP, the RP can verify that the message originated from the OP.
If these checks fail the RP should respond with a 404 HTTP error code, which tells the OP to stop sending updates. If the message is valid, the RP can update the user’s profile data.
While the Attribute Exchange extension provides significant features above those provided by Simple Registration, but it still has its limitations:
- Any attribute values provided to the RP are self-asserted.
- Related to the above, there is no way for a third party to make assertions about attribute values.
For (1), the solution is to perform the same level of verification on the attribute value as if the user had entered it directly. So an OpenID enabled mailing list manager should verify the email address provided by attribute exchange before subscribing the user. In contrast, an OpenID enabled shop probably doesn’t need to do further verification of the user’s shipping address (since it is in the user’s best interest to provide correct information).
The exception to this rule is when there is some other trust relationship between the OP and RP. For instance, if the RP knows that the OP will only send an email address if it has first been validated, then it may decide to trust the email address without performing its own validation checks. This is most likely to be useful in closed systems that happen to be using OpenID for single sign-on.