Protocol Description

The purpose of this protocol is to be able to provide an OpenID RP with an identity assertion and OAuth access token at the same time. This should be possible whether or not the RP/Consumer shares a pre-arranged consumer secret with the IdP/Service Provider. We call an RP/consumer that does not have a secret shared with the IdP/Service provider "stateless" (aka "dumb" in OpenId parlance).

Our first version of this protocol attempted to leave as much of OpenID and OAuth intact as possible, inventing new messages only when absolutely necessary. This gave us a protocol with some inefficiencies: there are cases where a relying party needs to make two separate requests to the service provider to complete the protocol. If we are willing to invent new messages we can combine those requests into a single step. The problem with inventing new messages is it forces the protocol to diverge further from both OpenID and OAuth.

The protocol below describes an approach that uses such new combined message types for efficiency. We call out in blue alternatives that would use standard OpenID and OAuth messages for improved compatibility.

Notation: Optional fields between []

Stateless

0) RP performs discovery (either online or finds some cached answer) and locates the IdP authentication endpoint, and obtains from the IdP an XRDS document describing the OpenID endpoints, and the OAuth endpoints.

1) RP sends OAuth request token request to IdP. IdP returns OAuth request token and request token secret.

For most IdP's it is important that the request token be stateless (i.e. they don't have to save state associated with the request token on the server). The most common case will be for a user to return an RP that already has access to their data, in which case the request token will never be exchanged for an access token. We should avoid creating persistent state on every authentication request. As a general rule, persistent state should only be necessary when a user changes their preferences in regard to a particular service provider.

2) RP sends OpenID authentication request via browser redirect to IdP.

openid.ns=http://specs.openid.net/auth/2.0

openid.mode=checkid_setup

[openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select or a URL for verification]

[openid.identity=http://specs.openid.net/auth/2.0/identifier_select or a URL for verification]

openid.return_to=http://www.relyingparty.com/openid/return

openid.ns.oauth=http://oauth.net/specs/openid

openid.oauth.reqtoken=<request-token>

[openid.oauth.scope=contacts calendar]

Note the scope parameter: contacts and calendar are SP-proprietary scopes.

3) IdP authenticates the user and looks up the RP based on their return_to URL (via OpenID discovery). If the user has not approved the RP's access to all of the openid.oauth.scope parameters, the user will be prompted to approve. (Do you want to give relyingparty.com access to your address book and your calendar?) The IdP may choose to confirm access even if the user has approved the RP before. (Do you still want the RP to have this information?)

If the user denies access, the protocol stops. If the user confirms access, the flow continues. The IdP saves the information about the user, the relying party, and the scope. The IdP also creates a "callback token" that is returned with the OpenID authentication response. This is used to verify that the OAuth access token is granted to the same entity that received the OpenID authentication response. Again, the callback token is unlikely to be used for the case where a user is returning to an RP that already has the appropriate OAuth access tokens, and so should be stateless.

4) IdP sends an OpenID authentication response via browser redirect to RP

openid.ns= http://specs.openid.net/auth/2.0

openid.mode=id_res

openid.op_endpoint=http://www.google.com/accounts/openid/identity

[openid.claimed_id=http://jsmith.blogspot.com]

[openid.identity=http://jsmith.blogspot.com]

openid.return_to=http://www.relyingparty.com/openid/return

openid.response_nonce=<nonce>

openid.assoc_handle=<private association handle>

openid.ns.oauth=http://oauth.net/specs/openid

openid.oauth.scope=calendar (in this example, user did not approve access to contacts)

openid.oauth.callbacktoken=<new value created by IdP>

openid.signed=<list of all fields in message>

openid.sig=<signature>

5) The RP then sends a request to the op_endpoint to verify the signature on the authentication response. This message contains a copy of all the fields in the openid authentication response, and one optional field:

[openid.oauth.accesstokenmsg=<signed OAuth access token request, including the callback token>]

For example:

openid.oauth.accesstokenmsg=oauth_token=<request-token>&oauth_callback_token=<callback-token>&oauth_consumer_key=<consumer-key>&oauth_signature=<signature>

The RP sends the access token message if they have not heard of this user before and believe they are going to need an OAuth access token for the user. They omit this field if they already have an access token for the user.

(Requiring the request token and request token secret here is important for the security of the protocol for unregistered consumers; by requiring the request token, the request token secret, and the callback token we prevent someone who can eavesdrop on only the callback token from getting an access token for the user.)

An alternative to message (4) would be to send a standard OpenID check_authentication message, plus a standard OAuth access_token request (which would simply use an empty consumer key, consumer secret, and request token secret). These two messages could either be sent in series or in parallel.

6) The IdP returns a message verifying the signature is correct. If the RP sent oauth.accesstoken=true, the IdP also returns

openid.oauth.accesstoken=<access token>

openid.oauth.tokensecret=<access token secret>

[openid.oauth.tokenexpires=<date on which token becomes invalid>]

(These tokens are probably fairly short lived, say 30 minutes to a few hours. We have to do this because if the user is on an open wireless network anyone eavesdropping will get a copy of the request token, and thus a copy of the access token as well. We don't want to give out indefinite access to a user's data)

7) The RP can now use the access token to access the user's data.

Refreshing the token in the stateless protocol

The access token expires fairly frequently. Because we do not have a consumer key and secret to identify the RP in server to server calls, the only way to refresh the token is via a browser redirect. The browser redirect takes the exact same form as the authentication request and response protocol above.

Stateful protocol

The stateful protocol is similar.

1) Identical.

2) The RP sends their association handle in the authentication request.

3) Identical.

4) The IdP signs the authentication message with the shared association.

5) The RP verifies the authentication message, then decides whether they want a new access token for the user. If they want an access token, they send the openid authentication request to the IdP authentication response endpoint, and sign the message with their consumer key and consumer secret. (Details of this signature TBD.)

An alternative to message (5) would be to use the standard OAuth access_token request (which would use the pre-arranged consumer key and secret, but an empty request token secret).

6) The IdP returns a new access token, token secret, and optional token expiration date. The IdP may also return a session renewal handle (as in the scalable OAuth extension).

7) The RP can sign requests to the identity endpoint with their consumer key, consumer secret, access token, and token secret.

Refreshing the token in the stateful protocol

Refreshing the token in the stateful protocol is easier: send another message to the authentication response endpoint with a copy of the session renewal handle. The IdP will return a refreshed access token. No browser redirect is necessary.

An alternative to this approach would be to use the access_token refresh message defined in the scalable OAuth extension, using the consumer key, consumer secret, access token and access token secret, as well as the session_handle returned in step (5).