First I obtained an OpenID account with www.myopenid.com. I actually have several other accounts with other OpenID Providers, such as pip.verisignlabs.com and yahoo.com because some relying parties allow only white-listed Providers, and some services offer me an OpenID whether I use it or not.
But to avoid an identity crisis of appearing all over the web as http://andrew.arnott.myopenid.com, http://andrewarnott.signon.com, http://aarnott.pip.verisignlabs.com, etc., I wanted to tie all these logins together under one identifier that I would always use, so people would be able to recognize the same person is behind all these identifiers. Using an XRDS document, I can do this. I created this document to describe all my OpenID Provider accounts:
<%@ Page ContentType="application/xrds+xml" %><?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="10"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <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://specs.openid.net/extensions/pape/1.0</Type> <URI>https://www.myopenid.com/server</URI> <LocalID>http://andrew.arnott.myopenid.com</LocalID> <openid:Delegate>http://andrew.arnott.myopenid.com</openid:Delegate> </Service> <Service priority="20"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://specs.openid.net/extensions/pape/1.0</Type> <URI>https://open.login.yahooapis.com/openid/op/auth</URI> <LocalID>https://me.yahoo.com/a/cJASAdp4x5Rx6CU9olKi7rMkG1TX_7Yl1kQ-</LocalID> </Service> <Service priority="30"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://openid.net/signon/1.0</Type> <URI>https://www.signon.com/partner/openid</URI> <LocalID>https://andrewarnott.signon.com</LocalID> <openid:Delegate>https://andrewarnott.signon.com</openid:Delegate> </Service> <Service priority="40"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://openid.net/signon/1.0</Type> <URI>https://pip.verisignlabs.com/server</URI> <LocalID>https://aarnott.pip.verisignlabs.com</LocalID> <openid:Delegate>https://aarnott.pip.verisignlabs.com</openid:Delegate> </Service> </XRD> </xrds:XRDS>
There is a little ASP.NET tag at the beginning to make sure the server sends down the proper Content-Type HTTP header with the document so Relying Party web sites know what they’re looking at. I was also careful to include the right <Type> tags in each service, as some services support just OpenID 1.x, some just 2.0, and some both. Some support extensions as well so I included those. Finally, each service has a priority attribute on it that allows the RP to sort the list based on my preferences and then choose the first Provider that fulfills the RP’s requirements.
This XRDS document, at a URL, could serve as my OpenID URL itself. But http://someserver/somexrds.aspx would be an ugly OpenID URL. So I needed to refer to this document from a URL that was easier on the eyes and the fingers.
First I had to choose an Identifier that I would always use. I did not want to use any Identifier that is specific to an individual OpenID Provider for two reasons:
- I might want to stop using that Provider at some point in the future.
- Most Providers do not let me add additional services to the XRDS document that they host for me.
A common choice is a blog URL. I had already been using this snippet on my blog to host an OpenID identity:
<link rel="openid.server" href="https://www.myopenid.com/server"/>
<link rel="openid.delegate" href="http://andrew.arnott.myopenid.com"/>
<link rel="openid2.provider" href="https://www.myopenid.com/server"/> <link rel="openid2.localid" href="http://andrew.arnott.myopenid.com"/>
This link style has the limitation of only allowing one of my Providers to be listed. But some RPs only support this syntax and cannot read XRDS documents, so while leaving this snippet here, above it I inserted the following snippet (within my Blogger-hosted blog’s HEAD section):
<meta content='http://nerdbank.net/openid_xrds.aspx' http-equiv='X-XRDS-Location'/>
Great. Now http://blog.nerdbank.net is my omni-identity. I can log into any OpenID relying party web site, and (assuming that site uses a decent implementation of OpenID) the RP will let me log in even if my first choice Provider isn’t strong enough by looking down the list until it finds one that is. If I don’t have any that are good enough, I can just create an account at one more Provider and add it to my list and I don’t have to worry about changing my OpenID URL that I use everywhere.
For example, Microsoft HealthVault has a whitelist of only two allowed Providers (including Verisign) that their visitors may log in with. No problem. I just log in as http://blog.nerdbank.net and the RP should (HealthVault actually doesn’t support all this yet) find the Verisign service in my XRDS document and automatically direct me to Verisign for authentication, even though it’s nearly last on my preferred list of Providers.
To summarize: I now have http://blog.nerdbank.net as my OpenID that I can use to log into any OpenID 1.x or 2.0 relying party web site (provided it has a decent implementation), regardless of what Providers each site may require I use.
In my next post, I’ll discuss how to use XRI i-names to further secure your identity.
This is not the right place to comment, I’m sure, but today I stombled on
http://www.squaredroot.com/post/2008/04/OpenID-Check_Authentication.aspx
and your comment
Security hole
Hi Troy,
This is a good addition to Mads’ implementation, but unfortunately this also has a security hole that would allow me to log in as anyone. Email me and I’ll explain what you need to do to plug it. (don’t want to make it public, you know).
Would you mind to send me the trick too? neil.young@freenet.de
Regards
Hi Neil,
As I recall (it’s been a while since I reviewed Mads’ and Troy’s code) the security hole is that CheckAuthentication implicitly trusts the op_endpoint parameter. As per the OpenID 2.0 spec section 11.2 this parameter and a few others must be verified either by rediscovery or by recalling a previous discovery and comparing the results with what is included in the positive assertion. Without this very critical step, any OP (fake or otherwise) can send a positive assertion for any old identifier whether it has authority to speak for it as well. Put more directly, I can login as you, just because my OP says so… unless you do this rediscovery step.
Hope this helps. But as usual on my soap box, I encourage you to ditch the custom implementation and go with a well-tested library to avoid many other pitfalls.