Thursday, July 03, 2008

How to use DotNetOpenId's Attribute Exchange extension

DotNetOpenId supports OpenID extensions, but does not provide samples for using any except the Simple Registration extension.  Since the Attribute Exchange extension (AX) was added recently, which is more extensible and generally usable than Simple Registration, sites using OpenID should consider using this extension, perhaps in addition to Simple Registration, to ease your web visitor's new user registration experience.  Since documentation on how to get started using AX is wanting currently, this post will describe how to get started using it.

I'll first describe a Relying Party's point of view in using AX, then I'll move on to how a Provider would do it.

Attribute Exchange for Relying Parties

First you need your IAuthenticationRequest instance.  Unfortunately the OpenIdLogin and OpenIdTextBox controls do not offer a chance for you to get at the IAuthenticationRequest instance, so if you're using these controls you'll need to drop down to calling the underlying API DotNetOpenId exposes.  But it's not hard even at the API level.  I covered it in my last post.  In fact I'll refer to it directly and assume you have that post as a starting point for brevity's sake in this post.

In the code behind snippet of my previous post, I left two placeholder comments for where extensions could be added to a request and read back from a response.  I'll provide the implementation for that now. 

First add a using statement for the AX extension:

using DotNetOpenId.Extensions.AttributeExchange;

Around line 21 of the code-behind snippet of my last post, just after acquiring IAuthenticationRequest through a call to OpenIdRelyingParty.CreateRequest(string), add code similar to the following:

var fetch = new FetchRequest();
fetch.AddAttribute(new AttributeRequest(WellKnownAttributes.Contact.Email));
fetch.AddAttribute(new AttributeRequest(WellKnownAttributes.Name.FullName));
request.AddExtension(fetch);

The above asks for the authenticating user's full name and email address. Feel free to explore the AttributeRequest constructor's other overloads, as more flexibility that is not obvious here is present, but I am using the simplest case for demonstrative purposes.

Now we need to add code to listen for the OpenID Provider's response and hope that it includes our visitor's name and email address. Add code like the following after you've determined that OpenIdRelyingParty.Response.Status is AuthenticationStatus.Authenticated. In my last post this is on line 42.

var fetch = openid.Response.GetExtension<FetchResponse>();
if (fetch != null) {
	IList<string> emailAddresses = fetch.GetAttribute(WellKnownAttributes.Contact.Email).Values;
	IList<string> fullNames = fetch.GetAttribute(WellKnownAttributes.Name.FullName).Values;
	string email = emailAddresses.Count > 0 ? emailAddresses[0] : null;
	string fullName = fullNames.Count > 0 ? fullNames[0] : null;
}

The collection semantics around the email and name, which otherwise makes this code look complicated, actually provide a powerful mechanism to support more than one value for a given requested field. Suppose a user has multiple home email addresses or home phones and wishes to supply them all. AX supports this. As a result however, we have to sift through collections for each field response even if we just want 0 or 1 value. In the code above we merely set that value or null to local variables. Of course you'll want to do something more meaningful with them like store them in a database or in the session state.

As with all OpenID extensions, merely sending a request with an extension in it does not guarantee that the Provider will send a response to that extension back.  The Provider may not support that extension, or may choose to not send any response. 

Also keep in mind that Providers that support AX and/or Simple Registration are allowed to not provide fields you ask for, even if you mark them as 'required'.  That is, you may ask for name and require email, but you may get name and not get email if that's what the Provider decides to give you (typically by asking the authenticating user what information he/she wants to provide).

Attribute Exchange for Providers

I will assume you already know how to use DotNetOpenId to implement a Provider.  And if you don't, you can gloss over the ProviderPortal sample that's included in the library download.  In that sample is a decide.aspx.cs file that sends the positive authentication assertion in the Yes_Click method.  The sample already responds to the Simple Registration extension.  This is an alternative implementation that provides the two fields we requested in our Relying Party sample built above.

protected void Yes_Click(Object sender, EventArgs e) {
	var axRequest = ProviderEndpoint.PendingAuthenticationRequest.GetExtension<FetchRequest>();
	var axResponse = new FetchResponse();
	AttributeRequest fullName = axRequest.GetAttribute(WellKnownAttributes.Name.FullName);
	AttributeRequest email = axRequest.GetAttribute(WellKnownAttributes.Contact.Email);
	if (fullName != null) {
		axResponse.AddAttribute(fullName.Respond("Andrew Arnott"));
	}
	if (email != null) {
		axResponse.AddAttribute(email.Respond("myemail@mycompany.com"));
	}
	ProviderEndpoint.PendingAuthenticationRequest.AddResponseExtension(axResponse);

	ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
	Debug.Assert(ProviderEndpoint.PendingAuthenticationRequest.IsResponseReady);
	ProviderEndpoint.PendingAuthenticationRequest.Response.Send();
	ProviderEndpoint.PendingAuthenticationRequest = null;
}

This checks whether the full name and email fields are requested and provides values for them if they are. Again, there is more flexibility here than I'm demonstrating. For example I'm not checking whether a field is marked as required or merely requested.

Obviously there is more work to fully support AX in the decide.aspx page than just changing one method--the rest of the page still is using Simple Registration. But I hope this demonstrates all the critical parts that you need to get going.

Summary

DotNetOpenId's support for Attribute Exchange is pretty easy to use if you know where to look and what to expect. Most of you probably haven't read the AX specification, so some of the API may surprise you like the support for multiple values for a single field. But now that you've seen the ropes, go out and support one of the greater OpenID extensions on your site!

5 comments:

  1. fetchresponse is always coming as null. any idea why?

    ReplyDelete
  2. nair,

    It results in null if the Provider did not provide an AX Fetch response in the message. Not all providers support AX. If you have more specifics and can send them to dotnetopenid@googlegroups.com I can answer more specifically.

    ReplyDelete
  3. Hi, I've got a similar problem.
    My 'FetchRequest' at the provider side is always NULL, but if I try to login from the same site - everything works...

    ReplyDelete
  4. Hi Andrew,

    I just followed code shown in the blog and has googled for AttributeExchange, but I could n't find a proper solution to get the user email, full name or username from openid provider.

    Is that AttributeExchange works for atleast once. They have everything in spec. but nothing works. My openid.Response.GetExtension<FetchResponse>() returns always null for me.

    ReplyDelete
  5. See my latest post on the AX problem:http://blog.nerdbank.net/2009/06/help-is-coming-for-sregax-interop.html

    ReplyDelete