How to Accept OpenID in a Popup Without Leaving the Page

February 04, 2009

For most sites that accept OpenID today, the user experience is one of two things:

  • User is redirected to the OpenID provider, and then redirected back to the original site. This is the most popular one, but it’s a particularly jarring experience for the user.
  • User is given a Javascript browser popup, but when the popup returns, it still refreshes the whole page. I haven’t actually seen this in the wild, but I’ve heard it discussed.

There has been some discussion lately about how the OpenID experience can work within a popup window. Next week, Facebook is hosting an OpenID design summit to work through some of the issues around cohesive design within a popup. However, one question that I’ve heard several times is “How does the popup work for Facebook Connect? Can it be done for OpenID?”

In theory, yes. In this blog post, I’ll walk through an approach for how the existing OpenID 2.0 spec can be used to do the entire exchange within a browser popup, so that the page doesn’t even have to refresh. Once the user is logged in, she can just keep doing what she was doing. At the moment this is just theoretical, but I will put up sample code and a demo once I get it all working.

Update: Brian Ellin implemented this idea in, like, an hour. His sample code implements a slightly simpler version of the technique described here. It’s really quite good.

See it in action here.

How it works

Okay, so suppose the user is on a page, and they see a “Sign in with OpenID” button. I’ll assume they have some way of choosing their provider. In the image below, I use the “Sign in with Yahoo” button. The user clicks the button, which triggers a Javascript handler. That handler sets up a callback (I’ll get to that later) and then calls window.open to initiate the transaction.

The first part of any OpenID transaction is discovery - that is, given a URL like “yahoo.com”, where do I go to actually log the user in? The RP also needs to establish a secure association, so that it can verify the signature on the response for security.

We open the popup onto a helper file located in the domain of our site. That helper file does the appropriate discovery, and then does a redirect to the OpenID provider, which remains within the browser popup. The OpenID provider walks the user through the steps to log in. It doesn’t even know it’s in a popup - as far as the provider is concerned, this is a full page (Although there are some discussions about how to let that it’s in a popup, nothing has made it into the spec yet).

The OpenID provider within the popup looks something like this:

Finally, the provider redirects back to the openid.return_to url. Remember in the first step, before the popup was even opened, the Javascript handler set up a callback? Well, the return_to url is a specially encoded cross domain url. It encodes information about that callback so that it can be decoded on the reply.

Here’s an example of a return_to cross-domain url:

`
http://open.sociallipstick.com/openid/xd_receiver.htm#fname=_opener
&%7B%22t%22%3A3%2C%22h%22%3A%22openIDresponse%22%2C%22sid%22%3A%220.672%22%7D
`

The cross-domain URL reads the response parameters, and just passes them directly to the parent page using Javascript. Because the cross-domain receiver and the parent page are on the same domain, the communication can proceed.

What next? The parent document now has all the OpenID parameters, but it’s in Javascript. The last step is to verify those parameters. The RP can make an Ajax call to a helper script, which looks up the association and verifies the signature. It can then perform any logging in, and pass back a success variable to the Javascript to let it know that everything went well. Of course, if the Javascript wants it can then go ahead and refresh the page, but if the user is in the middle of something, it can wait until the user is done to do so.

Performance

What about performance? This flow involves the following HTTP requests:

  1. The initial request for the RP page.
  2. The load of the helper page in the popup (which does background discovery)
  3. The redirect to the OP
  4. Probably, at least one form submit within the OP. (The user enters their name and password, and submits)
  5. The load of the reciever file when the OP redirects back
  6. The final Ajax call to the RP server to validate the signature

At least two of these HTTP requests can be optimized away:

  • Load of the helper popup. In the common case, users will be using an OpenID provider that’s been used before. For example, if someone clicks the “Yahoo” button, then the RP doesn’t need to do discovery on Yahoo.com again. In fact, the RP should cache both the server endpoint and the secure association, and make them available in Javascript. If that were the case, then the popup could open directly onto the OP site, and skip this one.
  • Load of the receiver page. The client needs to redirect to a page that lives on the RP domain- but that doesn’t mean that it has to load that page from the server. If the RP serves the cross domain receiver as static HTML with long cache headers, then the user’s browser will cache the page. As long as the query string doesn’t change, the browser won’t need to fetch the reciever again.

    But how can the query string not change? After all, the OpenID parameters need to be sent back in the query string, right?

    The way around this is to send the parameters back not in the query string but the fragment. So the return would look like:

    http://sociallipstick.com/receiver.htm#handler_info&**openid.ns=…..**

    Because the part before the fragment doesn’t change, this file never needs to be reloaded, and this HTTP request is saved.

Conclusion

The techniques laid out here can help the OpenID user experience reach the same level of fluidity as that achieved by Facebook Connect. Now it just remains to get some working code, and put it in practice!