l'essentiel est invisible pour les yeux

Showing posts with label OpenID. Show all posts
Showing posts with label OpenID. Show all posts

Friday, July 18, 2008

OpenID動向 - Trusted Data Exchangeがユーザ属性ポータビリティの切り札になるか?

第三回 Liberty Alliance 技術セミナーで=natさんの基調講演を公聴してきて、OpenID関連の動向を仕入れたのでメモ。

OPとRP間で属性情報を交換するための拡張として、SREGやAXがあるが、AXも今ひとつ普及するに至っていない。(myopenid.comとVeriSignだけ?)また、プライバシポリシーや利用規約の問題もあり、日本企業では、ユーザ属性をサードパーティに公開出来ない事も多い。

基本的に、AXとSREGの違いは、

  • 交換可能な属性をコミュニティベースで提案&決定する
  • 属性を識別するために、プリミティブな値でなく、ネームスペースURIを使う
  • OPに要求する属性の個数が指定可能
  • RPがOPに対して属性の保存をする事が可能。(ただし、使われているプロバイダは知らない。)


が挙げられるが、根本的な問題は、OpenIDのユーザ属性として、クレジットカード番号や電話番号などプライバシー性の高い情報を使う事など、気が狂っていると思われている事だ。AXへの本格的な以降が進まないのは、結局OpenIDで扱うユーザ属性は、ニックネームやメールアドレスのみで、SREGで十分に事が足りているからではないだろうか。

さて、単純なユーザ属性の交換と、OP側でのユーザの許可の仕組みしか提供していないAXに代わる仕様として、Trusted Data Exchangeが=natさん、=masakiさん (NRI)から提出されている。

TXのコンセプトには次のような物が含まれる。
  • 契約ベースの属性提供
  • XML Encryption/XML Signatureによる暗号化•書名
  • 非同期な属性提供 (非否認性を持った契約ベース)
  • RPの信憑性を判断するReputation Platform (現状のホワイトリストに代わる)
仕様は既に、提案済みで、仕様が取り込まれる方向で話が進んでいる様子。OpenIDによるログインフォームは、会員登録の敷居を下げる。結果的に、サービスを利用する敷居が下がり、クレジットカートや住所などの個人情報が信頼できないRPに渡る可能性が高くなる。ユーザ属性ポータビリティには、Reputation Platformの構築は、急務であると言える。

年内に取り込まれるだろうか?

もう一つの最新動向は、PAPEの仕様拡張。PAPEと言えば、OpenIDの認証強度を明示するためのOpenID拡張だが、採用するセキュリティモデルを明示的に指定するための仕様を提案しているとの事。現状のPAPEでは、NISTの定めるセキュリティモデルを指定できるだけであるが、日本のFISCの基準を採用する際には、次のように指定できるとの事だ。


openid.pape.auth_level.fisc:2
openid.pape.auth_level.ns.fisc: http://www.fisc.or.jp/ex/authlevel


See also: まちゅダイアリー - SAML と OpenID と CardSpace

Monday, April 14, 2008

OP Identifier and Claimed Identifier, easier OpenID Identifier.

It might be less well know, we can sign in with only OP Identifier(myopenid.com, yahoo.com etc.) into RP respond to OpenID authentication. For example, I have a myOpenID account in Rakuto Furutani. So I can sign in my OpenID URL (aka OP-Local Identifier), but I can also sign in into RP with only a OP's host name.



Why can we sign in with only a OP's host name(aka OP Identifier)?

1. First the OpenID library obtain Yadis Document from OP Identifier, It can emulate with curl script. This specification is part of a Yadis Specification.


% curl -H "Accept: application/xrds+xml" http://www.myopenid.com/

Take a response as XRDS

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
xmlns:xrds="xri://$xrds"
xmlns="xri://$xrd*($v*2.0)">
<XRD>

<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://openid.net/sreg/1.0</Type>
<URI priority="0">http://www.myopenid.com/server</URI>
</Service>

</XRD>
</xrds:XRDS>


The OpenID client can obtain OP Endpoint URL, "http://www.myopenid.com/server", and the client is initiate the OpenID Authentication request with obtained OP Endopoint URL. If authentication will be success, take a response(aka Positive Assertion). It includes such as some paramters is as follows:


{"openid.claimed_id"=>"http://rakuto.myopenid.com/",
"openid.mode"=>"id_res",
"openid.return_to"=>"http://localhost:3000/consumer/complete",
"openid.sig"=>"4fAHQGtaUgCjsrMAfue/Qi+k32w=",
"openid.ns"=>"http://specs.openid.net/auth/2.0",
"openid.op_endpoint"=>"http://www.myopenid.com/server",
"openid.response_nonce"=>"2008-04-14T13:27:18ZCEGxaV",
"openid.identity"=>"http://rakuto.myopenid.com/",
"openid.assoc_handle"=>"{HMAC-SHA1}{4e02f931}{EAZGvE==}",
"openid.signed"=>
"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed"}


The client library can obtain Claimed ID from openid.identity, and it can also obtain our XRDS.

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
xmlns:xrds="xri://$xrds"
xmlns:openid="http://openid.net/xmlns/1.0"
xmlns="xri://$xrd*($v*2.0)">
<XRD>

<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/extensions/sreg/1.1</Type>
<Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>http://www.myopenid.com/server</URI>
<LocalID>http://rakuto.myopenid.com/</LocalID>
</Service>

<Service priority="1">
<Type>http://openid.net/signon/1.1</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/extensions/sreg/1.1</Type>
<Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>http://www.myopenid.com/server</URI>
<openid:Delegate>http://rakuto.myopenid.com/</openid:Delegate>
</Service>

<Service priority="2">
<Type>http://openid.net/signon/1.0</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/extensions/sreg/1.1</Type>
<Type>http://schemas.openid.net/pape/policies/2007/06/phishing-resistant</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>http://www.myopenid.com/server</URI>
<openid:Delegate>http://rakuto.myopenid.com/</openid:Delegate>
</Service>

</XRD>
</xrds:XRDS>



Conclusion
If you use URL as OpenID Identifier, then you don't need to input "http://" and your Claimed Identifier because It's client library's work. Your Claimed Identifier is obtained by RP, so you just have to input your OPs host name.

e.g.) myopenid.com or yahoo.com etc.

Monday, March 24, 2008

[ruby] Why does StoreRequest of OpenID Attribute Exchange (AX) attract our heart? (and DEMO of AX StoreRequest with ruby-openid)

AX StoreRequest message is defined in OpenID Attribute Exchange 1.0. It can store personal identity information to the OpenID Provider from RP. It's exciting specification because we use many services on WWW, and input variety of many personal identity in sites. We can aggregate our personal identity automatically with AX StoreRequest message! Additionally, updated personal identity is synced AX's update_uri property across the all services!

Demo that sent AX StoreRequest message, and dispatch AX StoreResponse message with ruby-openid as follows.

At first, we need to extend ruby-openid to handle AX StoreRequest and StoreResponse usefully.
openid_ax_ext.rb


require 'rubygems'
require 'openid/extension'
require 'openid/message'

module OpenID
module AX
class StoreRequest
# Extract a StoreRequest from an OpenID message
def self.from_openid_request(oid_req)
message = oid_req.message
ax_args = message.get_args(NS_URI)
return nil if ax_args.empty?
req = new
req.parse_extension_args(ax_args)
req
end
end

class StoreResponse
def self.from_success_response(success_response)
resp = nil
ax_args = success_response.message.get_args(NS_URI)
resp = ax_args.key?('error') ? new(false, ax_args['error']) : new
end
end
end
end


app/controller/consumer_controller.rb

# in ConsumerController#start
# AX StoreRequest message
if params[:use_ax_store]
ax_store_req = OpenID::AX::StoreRequest.new
ax_store_req.set_values('http://axschema.org/contact/email', %w([email protected] [email protected]))
ax_store_req.set_values('http://axschema.org/birthDate', %w(1985-03-12))
ax_store_req.set_values('http://axschema.org/contact/IM/Skype', %w(rfurutani))
oidreq.add_extension(ax_store_req)
oidreq.return_to_args['did_ax_store'] = 'y'
end


Next, OP handles the request included StoreRequest message.
app/controller/server_controller.rb

# in ServerController#start
# Check the AX StoreRequest
@ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)


And OP must save personal identity sent from RP.

# Extract Personal Identity from request and save it.
# Check the StoreaRequest for AX
ax_store_resp = nil
ax_store_req = OpenID::AX::StoreRequest.from_openid_request(oidreq)
if ax_store_req
begin
# I think all user's attribute should be saved, but table structure of RDBMS VERY statically.
# So we need to respond StoreResponse mode is 'store_response_failure'.
# In a nut shell, we may use XMLDB by now!
ax_store_req.data.each do |type_uri, values|
case type_uri
when 'http://axschema.org/email'
# XXX: This is dummy
user.emails.concat(values)
when 'http://axschema.org/birthDate'
# XXX: This is dummy
user.birth_date = Date.parse(values.first)
when 'http://axschema.org/contact/IM/Skype'
# XXX: This is dummy
# Just one
user.skype_id = values.first
else
ax_store_resp = OpenID::AX::StoreResponse.new(false, "#{type_uri} isn't supported Type URI in this service.")
end
end
user.save
rescue Error => e
ax_store_resp = OpenID::AX::StoreResponse.new(false, e.message)
end
end
oidresp.add_extension(ax_store_resp || OpenID::AX::StoreResponse.new)


This is the sample view for AX StoreRequest added the checkbox enable it.

<% if flash[:ax_store_results] %>
<div class='error'>
<%= flash[:ax_store_results] %>
</div>
<% elsif params[:did_ax_store] -%>
<div class='alert'>
Your Personal identity has saved.
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra d
ata</label><br/>
<input type="checkbox" name="use_ax" id="use_ax" /><label for="use_ax">Request registration data with AX</label><br/>
<input type="checkbox" name="use_ax_store" id="use_ax_store" /><label for="use_ax_store">Request store attributes with AX</label>
</form>
</div>


Confirmation of stored personal identities.
app/views/server/decide.rhtml

<% # XXX: Part of axschema -%>
<% axschema_map = {
'http://axschema.org/birthDate' => 'Birthdate',
'http://axschema.org/contact/email' => 'Email',
'http://axschema.org/contact/IM/Skype' => 'Skype'
} -%>
<% # Is StoreRequest included in request? -%>
<% if @store_req -%>
<div class="attention">
<p>And do you accept following data sent from RP?</p>
<dl>
<% @store_req.data.each do |k,v| -%>
<dt><%= axschema_map[k] -%>:</dt>
<dd><%= h v.join(', ') -%></dd>
<% end -%>
</dl>
</div>
<% end -%>


Finally, request is coming back to RP, so We need to handle success response.
app/controllers/consumer_controller.rb

# in ConsumerController#complete
# for AX store
if params[:did_ax_store]
ax_store_resp = OpenID::AX::StoreResponse.from_success_response(oidresp)
flash[:ax_store_results] = ax_store_resp.error_message unless ax_store_resp.succeeded?
end


Screenshot
These screenshot are capture output of above code.


Confirmation of stored personal identities.


Conclusion
This future is pretty interesting, but it isn't suppoted even myOpenID now. In the future, I think many OP supports AX StoreRequest. I wish!

Saturday, March 22, 2008

[ruby] myOpenID and OpenID Attributes Exchange, myOpenID isn't supported Type URI described in http://axschema.org/

JanRain's myOpenID is popular OpenID provider, supports SSL, SREG, some persona, authentication with Information Card, OpenID Attributes Exchange (but only some attributes), Pavatar and so on. JanRain also offers ruby-openid that implements OpenID 2.0 with Ruby.

myOpenID supports only some attributes that can be exchange with OpenID Attribute Exchange, but you should care about Type URI, because myOpenID doesn't support Type URI described on "http://www.axschema.org/types/". You should specify Type URI with "http://schema.openid.net/" instead.

Sample code that fetch some attributes from myOpenID is as follows. At first, you need to initialize your persona. My persona for work is "Rakuto Furutani".

ConsumerController#start in /path/to/rails_openid/app/controller/consumer_controller.rb


# AX
if params[:use_ax]
# Builds Fetch Request of Attributes Exchange
ax_req = OpenID::AX::FetchRequest.new
requested_attrs = [ ['http://schema.openid.net/namePerson', 'name', true],
['http://schema.openid.net/namePerson/friendly', 'nickname'],
['http://schema.openid.net/contact/email', 'email', true],
['http://schema.openid.net/contact/web/default', 'web_default'],
['http://schema.openid.net/contact/postalCode/home', 'postal_code'],
['http://schema.openid.net/person/gender', 'gender'],
['http://schema.openid.net/birthDate', 'birthDate'],
['http://schema.openid.net/contact/country/home', 'country'],
['http://schema.openid.net/pref/language', 'language']]

requested_attrs.each {|a| ax_req.add(OpenID::AX::AttrInfo.new(a[0], a[1], a[2] || false))}
oidreq.add_extension(ax_req)
oidreq.return_to_args['did_ax'] = 'y'
pp oidreq
end


Customize a view
/path/to/rails_openid/app/views/consumer/index.rhtml

<% if flash[:ax_results]%>
<div class='alert'>
<%= flash[:ax_results] %>
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra data</label><br/>
<input type="checkbox" name="use_ax" id="use_ax" /><label for="use_ax">Request registration data with AX (Attribute Exchange)</label>
</form>
</div>


ConsumerController#complete in /path/to/rails_openid/app/controllers/consumer_controller.rb

if params[:did_ax]
ax_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)
pp ax_resp
end


Screenshot


We can fetch some attribtues from myOpenID, above code's output is as follows:

OpenID::Consumer::CheckIDRequest instance

#<OpenID::Consumer::CheckIDRequest:0x33543fc
@anonymous=false,
@assoc=
#<OpenID::Association:0x3354500
@assoc_type="HMAC-SHA1",
@handle="{HMAC-SHA1}{47dd30ef}{WTRsAg==}",
@issued=Sun Mar 16 14:38:35 +0000 2008,
@lifetime=1209600,
@secret="\005F6\246W\032\220\233]\002V$j\034��j�!1">,
@endpoint=
#<OpenID::OpenIDServiceEndpoint:0x33602b0
@canonical_id=nil,
@claimed_id="http://rakuto.myopenid.com/",
@display_identifier=nil,
@local_id="http://rakuto.myopenid.com/",
@server_url="http://www.myopenid.com/server",
@type_uris=
["http://specs.openid.net/auth/2.0/signon",
"http://openid.net/sreg/1.0",
"http://openid.net/extensions/sreg/1.1",
"http://schemas.openid.net/pape/policies/2007/06/phishing-resistant",
"http://openid.net/srv/ax/1.0"],
@used_yadis=true>,
@message=
#<OpenID::Message:0x3354334
@args=
{["http://openid.net/srv/ax/1.0", "type.gender"]=>
"http://schema.openid.net/person/gender",
["http://openid.net/srv/ax/1.0", "if_available"]=>
"country,web_default,language,birthDate,postal_code,nickname,gender",
["http://openid.net/srv/ax/1.0", "type.language"]=>
"http://schema.openid.net/pref/language",
["http://openid.net/srv/ax/1.0", "type.postal_code"]=>
"http://schema.openid.net/contact/postalCode/home",
["http://openid.net/srv/ax/1.0", "mode"]=>"fetch_request",
["http://openid.net/srv/ax/1.0", "type.birthDate"]=>
"http://schema.openid.net/birthDate",
["http://openid.net/srv/ax/1.0", "type.country"]=>
"http://schema.openid.net/contact/country/home",
["http://openid.net/srv/ax/1.0", "type.nickname"]=>
"http://schema.openid.net/namePerson/friendly",
["http://openid.net/srv/ax/1.0", "type.name"]=>
"http://schema.openid.net/namePerson",
["http://openid.net/srv/ax/1.0", "required"]=>"name,email",
["http://openid.net/srv/ax/1.0", "type.web_default"]=>
"http://schema.openid.net/contact/web/default",
["http://openid.net/srv/ax/1.0", "type.email"]=>
"http://schema.openid.net/contact/email"},
@namespaces=
#<OpenID::NamespaceMap:0x33542d0
@alias_to_namespace=
{"ax"=>"http://openid.net/srv/ax/1.0",
:null_namespace=>"http://specs.openid.net/auth/2.0"},
@namespace_to_alias=
{"http://specs.openid.net/auth/2.0"=>:null_namespace,
"http://openid.net/srv/ax/1.0"=>"ax"}>,
@openid_ns_uri="http://specs.openid.net/auth/2.0">,
@return_to_args={"did_ax"=>"y"}>


OpenID::Consumer::SuccessResponse instance

#<OpenID::Consumer::SuccessResponse:0x32800c0
@endpoint=
#<OpenID::OpenIDServiceEndpoint:0x32bea28
@canonical_id=nil,
@claimed_id="http://rakuto.myopenid.com/",
@display_identifier=nil,
@local_id="http://rakuto.myopenid.com/",
@server_url="http://www.myopenid.com/server",
@type_uris=
["http://specs.openid.net/auth/2.0/signon",
"http://openid.net/sreg/1.0",
"http://openid.net/extensions/sreg/1.1",
"http://schemas.openid.net/pape/policies/2007/06/phishing-resistant",
"http://openid.net/srv/ax/1.0"],
@used_yadis=true>,
@identity_url="http://rakuto.myopenid.com/",
@message=
#<OpenID::Message:0x3296f78
@args=
{["http://openid.net/srv/ax/1.0", "type.gender"]=>
"http://schema.openid.net/person/gender",
["http://specs.openid.net/auth/2.0", "sig"]=>
"5aziAIn3yMlbxq8XFaF8hKG4GCk=",
[:bare_namespace, "did_ax"]=>"y",
["http://openid.net/srv/ax/1.0", "type.language"]=>
"http://schema.openid.net/pref/language",
["http://openid.net/srv/ax/1.0", "count.gender"]=>"1",
["http://specs.openid.net/auth/2.0", "mode"]=>"id_res",
["http://openid.net/srv/ax/1.0", "mode"]=>"fetch_response",
["http://openid.net/srv/ax/1.0", "type.postal_code"]=>
"http://schema.openid.net/contact/postalCode/home",
["http://openid.net/srv/ax/1.0", "value.gender.1"]=>"M",
["http://specs.openid.net/auth/2.0", "op_endpoint"]=>
"http://www.myopenid.com/server",
["http://specs.openid.net/auth/2.0", "response_nonce"]=>
"2008-03-22T04:51:54ZT0280h",
["http://openid.net/srv/ax/1.0", "type.birthDate"]=>
"http://schema.openid.net/birthDate",
["http://openid.net/srv/ax/1.0", "value.name.1"]=>"Rakuto Furutani",
["http://openid.net/srv/ax/1.0", "count.postal_code"]=>"0",
["http://openid.net/srv/ax/1.0", "value.nickname.1"]=>"rakuto",
["http://openid.net/srv/ax/1.0", "type.country"]=>
"http://schema.openid.net/contact/country/home",
["http://openid.net/srv/ax/1.0", "count.name"]=>"1",
["http://openid.net/srv/ax/1.0", "count.web_default"]=>"1",
["http://openid.net/srv/ax/1.0", "type.nickname"]=>
"http://schema.openid.net/namePerson/friendly",
["http://specs.openid.net/auth/2.0", "return_to"]=>
"http://localhost:3001/consumer/complete?did_ax=y",
["http://specs.openid.net/auth/2.0", "assoc_handle"]=>
"{HMAC-SHA1}{47dd30ef}{WTRsAg==}",
["http://openid.net/srv/ax/1.0", "count.email"]=>"0",
["http://openid.net/srv/ax/1.0", "type.name"]=>
"http://schema.openid.net/namePerson",
["http://openid.net/srv/ax/1.0", "count.nickname"]=>"1",
["http://openid.net/srv/ax/1.0", "count.country"]=>"1",
["http://specs.openid.net/auth/2.0", "identity"]=>
"http://rakuto.myopenid.com/",
["http://specs.openid.net/auth/2.0", "signed"]=>
"assoc_handle,ax.count.birthDate,ax.count.country,ax.count.email,ax.count.gender,ax.count.language,ax.count.name,ax.count.nickname,ax.count.postal_code,ax.count.web_default,ax.mode,ax.type.birthDate,ax.type.country,ax.type.email,ax.type.gender,ax.type.language,ax.type.name,ax.type.nickname,ax.type.postal_code,ax.type.web_default,ax.value.birthDate.1,ax.value.country.1,ax.value.gender.1,ax.value.language.1,ax.value.name.1,ax.value.nickname.1,ax.value.web_default.1,claimed_id,identity,mode,ns,ns.ax,op_endpoint,response_nonce,return_to,signed",
["http://openid.net/srv/ax/1.0", "count.language"]=>"1",
["http://openid.net/srv/ax/1.0", "value.language.1"]=>"JA",
["http://openid.net/srv/ax/1.0", "value.web_default.1"]=>
"http://raku.to/",
["http://openid.net/srv/ax/1.0", "value.birthDate.1"]=>"1985-03-12",
["http://openid.net/srv/ax/1.0", "type.web_default"]=>
"http://schema.openid.net/contact/web/default",
["http://openid.net/srv/ax/1.0", "type.email"]=>
"http://schema.openid.net/contact/email",
["http://openid.net/srv/ax/1.0", "value.country.1"]=>"JP",
["http://specs.openid.net/auth/2.0", "claimed_id"]=>
"http://rakuto.myopenid.com/",
["http://openid.net/srv/ax/1.0", "count.birthDate"]=>"1"},
@namespaces=
#<OpenID::NamespaceMap:0x329680c
@alias_to_namespace=
{"ax"=>"http://openid.net/srv/ax/1.0",
:null_namespace=>"http://specs.openid.net/auth/2.0"},
@namespace_to_alias=
{"http://specs.openid.net/auth/2.0"=>:null_namespace,
"http://openid.net/srv/ax/1.0"=>"ax"}>,
@openid_ns_uri="http://specs.openid.net/auth/2.0">,
@signed_fields=
[
"openid.assoc_handle",
"openid.ax.count.birthDate",
"openid.ax.count.country",
"openid.ax.count.email",
"openid.ax.count.gender",
"openid.ax.count.language",
"openid.ax.count.name",
"openid.ax.count.nickname",
"openid.ax.count.postal_code",
"openid.ax.count.web_default",
"openid.ax.mode",
"openid.ax.type.birthDate",
"openid.ax.type.country",
"openid.ax.type.email",
"openid.ax.type.gender",
"openid.ax.type.language",
"openid.ax.type.name",
"openid.ax.type.nickname",
"openid.ax.type.postal_code",
"openid.ax.type.web_default",
"openid.ax.value.birthDate.1",
"openid.ax.value.country.1",
"openid.ax.value.gender.1",
"openid.ax.value.language.1",
"openid.ax.value.name.1",
"openid.ax.value.nickname.1",
"openid.ax.value.web_default.1",
"openid.claimed_id",
"openid.identity",
"openid.mode",
"openid.ns",
"openid.ns.ax",
"openid.op_endpoint",
"openid.response_nonce",
"openid.return_to",
"openid.signed"]>


Extracted attributes from response - OpenID::AX::FetchResponse instance

#<OpenID::AX::FetchResponse:0x3254a4c
@data=
{"http://schema.openid.net/namePerson"=>["Rakuto Furutani"],
"http://schema.openid.net/contact/country/home"=>["JP"],
"http://schema.openid.net/contact/web/default"=>["http://raku.to/"],
"http://schema.openid.net/pref/language"=>["JA"],
"http://schema.openid.net/contact/email"=>[],
"http://schema.openid.net/birthDate"=>["1985-03-12"],
"http://schema.openid.net/contact/postalCode/home"=>[],
"http://schema.openid.net/namePerson/friendly"=>["rakuto"],
"http://schema.openid.net/person/gender"=>["M"]},
@mode="fetch_response",
@ns_alias="ax",
@ns_uri="http://openid.net/srv/ax/1.0",
@update_url=nil>

Conclusion about myOpenID
  • Supports only some attributes that can be exchanged with AX.
  • Doesn't support openid.ax.update_url yet.
  • Doesn't support Type URI described in axschema, you should specify Type URI with "http://schema.openid.net/" namespace.

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:

requirements:
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
oid_req.add_extension(ax_req)


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' => '[email protected]'.sub('nospam.', ''),
'value.email.2' => '[email protected]',
'type.birth_date' => 'http://axschema.org/birthDate',
'value.birth_date' => '1985-03-12',
'count.email' => 2}
ax_resp = OpenID::AX::FetchResponse.new
ax_resp.parse_extension_args(ax_args)
ax_args = ax_resp.get_extension_args(ax_req) # for validation
ax_resp.parse_extension_args(ax_args)

# pp ax_resp.get_extension_args(ax_req)
# {"count.email"=>"2",
# "type.email"=>"http://axschema.org/contact/email",
# "value.email.1"=>"[email protected]",
# "count.birth_date"=>"1",
# "count.nick"=>"1",
# "value.email.2"=>"[email protected]",
# "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"}

oid_resp.add_extension(ax_res)



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)
#<OpenID::AX::FetchResponse:0x33527a0
# @data=
# {"http://axschema.org/contact/email"=>
# ["[email protected]", "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.