OpenID for Desktop Clients
The itch I had is that I have a network client that communicates with a server using HTTP as a communication protocol (doesn't have to be HTTP, but its simpler to work with). It could be an IM client on the desktop, for example, that uses HTTP as a communication protocol (or any REST-based interface). They key is that the client and server share some persistent state (like a cookie-based session).
I want to have the user present their OpenID to the desktop client application and have them do normal OpenID authentication (which happens through the browser). Below, I describe what I implemented to make that happen (I implemented this with a simple python command-line desktop client, and a pylons-based openid provider, but the choice of implementation is not important - if you want to see my code, please ask - I haven't tied it up in a pretty bow yet).
The basic idea is to have the desktop client ask the server component to initiate openid through the client and then through a browser living on the user's desktop. That browser session and the client "session" with the application server are associated with a nonce (generated by the application server). From an OpenID perspective, the application server is the RP, and the OP is the same OP you would normally use with the openid. The desktop client is just a proxy for the app living on the desktop that kicks off the normal browser-based authentication session on behalf of application server.
Another way to think of it is that instead of a HTML Form POST initiating the OpenID authentication process, its the desktop application kicking off the authentication process.
Here's a rough description:
- Desktop client listens on localhost port 9876 (http) to receive notification that the openid auth session was successful or not. We'll see why later.
To initiate login, it first does a request to the server component to associate the desktop client's http session to the server component with an openid, and a nonce:
GET on http://servercomponent.com/client-auth/=gmw - Server component returns:
302 relocated
Location: http://servercomponent.com/client-login/<nonce>
In generating this 302 response, the server associates the nonce with desktop client's HTTP session, and the openid requested by the desktop client. - The desktop client gets the 302 and does NOT follow the redirect itself. Instead, it makes the local web browser (e.g. python's webbrowser.open) go to
http://servercomponent.com/client-login/<nonce>
Note: the desktop client and its HTTP session are now associated (by the server component) with the user's web browser session via the nonce. - The server component looks up openid by nonce and does openid discovery, returns a redirect to OP to the browser, with return_to set to:
http://servercomponent.com/client-complete/<nonce> - OpenID auth happens as normal (user browser goes off to OP to authenticate)
- The user browser eventually returns to
http://servercomponent.com/client-complete/<nonce> - In response to #6 server component returns a http redirect to http://localhost:9876 - this redirect can contain status information which lets the desktop client app know whether there was success or failure. Of course, the server component (on servercomponent.com) knows about the sucess or failure of course since it is a normal RP and has done normal OpenID auth in step 5. The desktop client, in response to this request, can try to close the browser window, or give the user a friendly status message.
The result is that the desktop client has gotten an indication that authentication was successful (though it could discover that from the servercomponent.com if it needed to). The server component knows that the session associated with the nonce is authenticated because that nonce was used to initiate openid on the browser. Thus, the client can be trusted by the server to be associated with the identifier that the client initially requested to be authenticated.
I'm trying to find issues with this approach - please do contact me with issues you do find.
Just understand that your idea still requires that the user *absolutely* trusts the client software with a username and password. Just because the client appears to be opening a regular browser window does not mean that it's not opening a second, hacked installation (modified upon the software client install).
Posted by: bmuller | March 22, 2007 at 05:30 PM
Here are a couple other variations on your theme:
-- Embed a Web browser in the desktop app itself, such as Gecko. That makes the OpenID work happen fully within the application rather than through an external browser. Monitoring browser progress through events might even eliminate the need to do the localhost:9876 socket.
-- When the user launches the "program", the icon or whatever spawns both a Web browser (visible) and your application (not yet visible, but with your localhost:9876 socket). The browser opens onto a traditional OpenID login page, and login proceeds like it would if they were logging into a local site. If you make your OpenID process be specific to your desktop app, the server can just "know" to redir to localhost:9876 upon completion, which triggers popping up the first application window and, perhaps, closing the browser.
Basically, I agree with bmuller that having login split between an app and a browser might be a bit disconcerting for users.
Posted by: Mark Murphy | April 06, 2007 at 03:45 AM
I've been thinking about a simular solution. (As you can see on my blog). I make the client act as a browser, instead of using a browser, but then some really good html form parsing code needs to be written.
But there are a few security implications, also noted in my blog.
Posted by: Timothy | April 30, 2007 at 01:34 AM
BTW, you should check out OAuth since this idea was core to the problem we set out to solve.
http://oauth.net
Posted by: Chris Messina | September 23, 2007 at 05:56 PM
Yes, OAuth is the answer here now! Everyone should go check out oauth.net for this problem now...
Posted by: Gabe Wachob | September 23, 2007 at 05:58 PM
Can you please provide me the code for this?
Posted by: Munish | November 17, 2008 at 03:22 AM