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.