l'essentiel est invisible pour les yeux

Monday, March 17, 2008

[ruby] Fetch and Store some attributes with OpenID Attributes Exchange 1.0

We have many profiles about ourselves in web applications, communities, organization, and so on. My profile in mixi and my profile in Facebook are different, my Blogger's profile and my HP's profile are different too. This problem more and more annoy us.

OpenID Atrributes Exchange 1.0
is an OpenID service extension for exchanging identity information between endpoint(Relying Party and OpenID Provider). We can think some scenarios. For example, we want to disclose about name to all services, but we think want to only disclose email or credit card number to the specified service.

As you know, OpenID has protocol for light-weight profile exchange. This protocols is known as OpenID Simple Registration Extension 1.1 (SREG), but SREG is obsolete now. The downside of SREG is that it only supported limited number of attributes. You should use OpenID Attributes Exchange for exchanging identity.

What can we do with OpenID Attribute Exchange?

  • Fetch some values -- (use fetch_request)
  • Store some values -- (use store_request)

What kind of attributes do we use with OpenID Attribute Exchange?
You can see exchanging identity in OpenID Attribute Exchange | Attribute Type. If the parameter you want doesn't exist, then you define new parameter. For details, see How do I define new attributes?. The attributes specified with Type URI unlike SREG.

http://axschema.org/namePerson/friendly => Alias/Username
http://axschema.org/contact/email => Email
http://axschema.org/contact/IM/Skype => Skype IM

Sample code with Attribute Exchange implementaion with ruby-openid as follows:

ruby-openid 2.0.4

The side of Relying Party
This example requires "nickname", "email" are required and "birth_date" and "phone" is obtained if available it. The code is beggining of OpenID Authentication. (ConsumerController#begin etc)

# NOTE: in your controller that start login with OpenID Authentication
consumer = OpenID::Consumer.new(session, store)
oid_req = consumer.begin(params[:openid_identifier])

# An attribute exchange 'fetch_request' message
ax_req = OpenID::AX::FetchRequest.new

# OpenID::AX::AttrInfo.new(type_uri, extension_alias=nil, required=nil)
# Specify required attributes with Attribute Exchange
attr_nickname = OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/friendly', 'nick', true)
attr_email = OpenID::AX::AttrInfo.new('http://axschema.org/contact/email', 'email', true)
attr_birth_date = OpenID::AX::AttrInfo.new('http://axschema.org/birthDate', 'birth_date', false)
attr_phone = OpenID::AX::AttrInfo.new('http://axschema.org/contact/phone/default', 'phone', false)
[attr_nickname, attr_email, attr_birth_date, attr_phone].each {|a| ax_req.add(a)}

# add extension with Attribute Exchange object

The side of OpenID Provider
You can extract extension fields from request with OpenID::AX::FetchRequest.from_openid_request method, and add some values with OpenID::AX::FetchResponse#parse_extension_args method. You can specify values with array. (value.ALIAS_NAME.1, value.ALIAS_NAME.2 etc).

oid_resp = oid.answer(true, nil, identity)

# XXX: In a real application, these attributes are user-specific
# and the use should be asked for permission to release these attributes
ax_req = OpenID::AX::FetchRequest.from_openid_request(oidreq)
ax_args = { 'mode' => 'fetch_response',
'update_url' => url_for(:controller => 'user', :action => 'update', :id => '1'),
'type.nick' => 'http://axschema.org/namePerson/friendly',
'value.nick' => 'rakuto',
'type.email' => 'http://axschema.org/contact/email',
'value.email.1' => 'rakuto@nospam.gmail.com'.sub('nospam.', ''),
'value.email.2' => 'rakuto@example.com',
'type.birth_date' => 'http://axschema.org/birthDate',
'value.birth_date' => '1985-03-12',
'count.email' => 2}
ax_resp = OpenID::AX::FetchResponse.new
ax_args = ax_resp.get_extension_args(ax_req) # for validation

# pp ax_resp.get_extension_args(ax_req)
# {"count.email"=>"2",
# "type.email"=>"http://axschema.org/contact/email",
# "value.email.1"=>"rakuto@gmail.com",
# "count.birth_date"=>"1",
# "count.nick"=>"1",
# "value.email.2"=>"rakuto@example.com",
# "value.birth_date.1"=>"1985-03-12",
# "mode"=>"fetch_response",
# "type.nick"=>"http://axschema.org/namePerson/friendly",
# "type.birth_date"=>"http://axschema.org/birthDate",
# "value.nick.1"=>"rakuto",
# "update_url"=>"http://localhost:3000/user/update/1",
# "type.phone"=>"http://axschema.org/contact/phone/default",
# "count.phone"=>"0"}


The side of Relying Party
Last come, the request returned to Relying Party. This code is part of end of OpenID authentication. (ConsumerController#complete etc)

consumer = OpenID::Consumer.new(session, store)
current_url = url_for(:action => 'complete', :only_path => false)
parameters = params.reject{|k,v|request.path_parameters[k]}
oid_resp = consumer.complete(parameters, current_url)

# Dump the exchanged attributes
# pp OpenID::AX::FetchResponse.from_success_response(oid_resp)
# @data=
# {"http://axschema.org/contact/email"=>
# ["rakuto@gmail.com", "rakuto@examhttp://www.blogger.com/img/gl.link.gifple.com"],
# "http://axschema.org/birthDate"=>["1985-03-12"],
# "http://axschema.org/namePerson/friendly"=>["rakuto"]},
# @mode="fetch_response",
# @ns_alias="ax",
# @ns_uri="http://openid.net/srv/ax/1.0",
# @update_url="http://localhost:3000/user/update/1">

OpenID Attributes Exchange is simple, but pretty interesting. Next time, I'm going to introduce "store_request" mode for storing value if value dont't exists.