[OAUTH-WG] OAuth abstractions

"Thomson, Martin" <Martin.Thomson@andrew.com> Thu, 21 October 2010 22:34 UTC

Return-Path: <Martin.Thomson@andrew.com>
X-Original-To: oauth@core3.amsl.com
Delivered-To: oauth@core3.amsl.com
Received: from localhost (localhost [127.0.0.1]) by core3.amsl.com (Postfix) with ESMTP id 4BD073A672E for <oauth@core3.amsl.com>; Thu, 21 Oct 2010 15:34:38 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.887
X-Spam-Level:
X-Spam-Status: No, score=-2.887 tagged_above=-999 required=5 tests=[AWL=-0.288, BAYES_00=-2.599]
Received: from mail.ietf.org ([64.170.98.32]) by localhost (core3.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 3qlAY-3leBLM for <oauth@core3.amsl.com>; Thu, 21 Oct 2010 15:34:35 -0700 (PDT)
Received: from csmailgw2.commscope.com (csmailgw2.commscope.com [198.135.207.242]) by core3.amsl.com (Postfix) with ESMTP id 1D2213A63EC for <oauth@ietf.org>; Thu, 21 Oct 2010 15:34:35 -0700 (PDT)
Received: from [10.86.20.103] ([10.86.20.103]:41938 "EHLO ACDCE7HC2.commscope.com") by csmailgw2.commscope.com with ESMTP id S459607Ab0JUWgL (ORCPT <rfc822; oauth@ietf.org>); Thu, 21 Oct 2010 17:36:11 -0500
Received: from SISPE7HC1.commscope.com (10.97.4.12) by ACDCE7HC2.commscope.com (10.86.20.103) with Microsoft SMTP Server (TLS) id 8.1.436.0; Thu, 21 Oct 2010 17:36:10 -0500
Received: from SISPE7MB1.commscope.com ([fe80::9d82:a492:85e3:a293]) by SISPE7HC1.commscope.com ([fe80::8a9:4724:f6bb:3cdf%10]) with mapi; Fri, 22 Oct 2010 06:36:06 +0800
From: "Thomson, Martin" <Martin.Thomson@andrew.com>
To: "oauth@ietf.org" <oauth@ietf.org>, Blaine Cook <romeda@gmail.com>
Date: Fri, 22 Oct 2010 06:36:03 +0800
Thread-Topic: OAuth abstractions
Thread-Index: ActxcFepJkAX4TmLREi76+eteSP7XQ==
Message-ID: <8B0A9FCBB9832F43971E38010638454F03F31EABAD@SISPE7MB1.commscope.com>
Accept-Language: en-US
Content-Language: en-US
X-MS-Has-Attach:
X-MS-TNEF-Correlator:
acceptlanguage: en-US
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
MIME-Version: 1.0
X-BCN: Meridius 1000 Version 3.4 on csmailgw2.commscope.com
X-BCN-Sender: Martin.Thomson@andrew.com
Subject: [OAUTH-WG] OAuth abstractions
X-BeenThere: oauth@ietf.org
X-Mailman-Version: 2.1.9
Precedence: list
List-Id: OAUTH WG <oauth.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/listinfo/oauth>, <mailto:oauth-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/oauth>
List-Post: <mailto:oauth@ietf.org>
List-Help: <mailto:oauth-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/oauth>, <mailto:oauth-request@ietf.org?subject=subscribe>
X-List-Received-Date: Thu, 21 Oct 2010 22:34:38 -0000

At the last meeting, I promised Blaine that I'd share a few thoughts on how I understood OAuth to operate.  It's taken me a while to find the time to marshal my thoughts, but here 'tis.

After attending the meeting and tutorial, it was clear that the abstraction behind OAuth is not being effectively communicated.  A simple abstraction can be a powerful tool in dealing with a complex specification.  It seems to me that OAuth struggles as a result of trying to cover so many use cases at once.  This might be easier if the abstract model were better understood.

I believe that OAuth has a very simple abstraction.  What I am going to try to do is describe that abstraction.  

The cunning techniques employed to circumvent technical limitations - redirection with URI fragments and so forth - are noise on the wire at this layer.  As a necessary product of having to reduce the abstraction to practice, they have little bearing on what I am going to attempt to describe.

I appreciate that the process is well beyond this airy-fairy stage.  If nothing else, I hope that this jostles a few brain cells into new ways of looking at OAuth.

To preface this with a warning: There's been a lot of discussion about the OAuth "protocol" and examination of its properties from the perspective of it being a thing that "uses HTTP".  There's a lot of HTTP experience that teaches the long term folly of trying to build on top of HTTP.  I'm probably going to beat on a dead horse by explaining where working _with_ HTTP (as opposed to _despite_ it) has significant advantages.


==First Abstraction

OAuth specifies a separation between authentication and authorization.

Never mind that HTTP got these mixed up early on, it doesn't now - HTTP only provides authentication.  The "Authorization" header is for authentication.  That might be implicitly used to infer authorization, but it's still fundamentally just authentication.

OAuth lets a Client come to a Resource Server with separate evidence for identity and authorization.

==Goal

A Client's goal is to acquire a resource from the Resource Server.  Or, to be more general, to successfully complete a particular type of (HTTP) request.

In order to do this, authentication is not sufficient, the Client must also prove that it is authorized.

The Client might be known to the Resource Server.  This is the authentication part.  There are existing mechanisms for that (the idea that these might be improved is a subject that comes up on occasion).  [1]

==Proving Authorization

Once the Client is authenticated (or not) the Resource Server might still reject the request.  There's an HTTP 403 (Forbidden) response for this purpose.

If this were at all like an HTTP 401 response, that rejection could provide the clues that could be used by the Client to determine how to acquire evidence of authorization (what OAuth calls the Access Token).  

I don't know if anyone has proposed this, but it seems logical to extend HTTP and work within its framework, thus:

  1. Add a header to the 403 (Forbidden) response.  This header would indicate that irrespective of the identity of the client, further evidence of authorization is required (that is, an Access Token).

  2. Add a request header that is intended to carry an access token.

The first might be a little complex.  Unlike the 401 response, OAuth has a number of variations on how an Access Token is required and this constrains the information that can be revealed at this point.  

This 403 header might include virtually no clues at all.  Maybe the equivalent of "realm" - some private identifier that helps those in the know distinguish between one set of authorization requirements from another.  What information is revealed depends on the desired security properties.

==Access Token

It's tempting to say that the Access Token is completely opaque to all but the Authorization Server and the Resource Server.  A private agreement between these parties could be used to determine what information is included.

In the abstract sense, the definition of the request header could just provide a bit bucket for a token.  In the macro, that's all the solution requires.

Obviously, the work doesn't stop there.  To ignore the access token entirely would be trivializing all the work that has been done in this area.  A separate authorization function is an important feature.  Structured agreements, such as the SAML assertions, have a significant role and formalizing formats can be extremely powerful.

Then there are the security concerns that arise when you wave your hands.  Saying "magic happens here" means that people create their own magic.  And that never really works out well.

So define two things:
 - how to acquire an access token
 - what the access token looks like, what data it contains, necessary bindings and so forth

The access token format is where you get into signatures and the like.  It also depends a lot on what the use cases are and the security properties that you desire.  I'm going to concentrate on the acquisition thing first and foremost.  The format should follow from that.

==Acquiring an Access Token

If the Access Token is view as an opaque lump of data [2], then there's an HTTP method that's well suited to this task: GET.

GET goes a surprisingly long way to accomplishing this task.

Say that the 403 response included - in the responses header, maybe a Link header - a URI.  That URI could be the URI for an Access Token resource.  Require authentication of the Client in order to successfully acquire the contents of this resource and you have dealt with the basic scenarios shown in Figures 2 and 3 of the -10 version of the spec.

This URI might not directly refer to the Access Token resource.  It could lead to a chain of HTML pages that need to be navigated successfully to get to the token.

The same basic abstraction also applies to the Access Grant.  The Access Grant and Access Token share a surprising number of common characteristics.  They both carry authorization information, independent of authentication.  The only difference is where they are acquired from.  An Access Grant is an Access Token for the Access Token resource.  Both are optional: the only difference being that if you don't use an Access Token, you aren't using OAuth.

What if the Access Grant and Access Token used the same mechanism?  Then you just have a chaining of the same process:

  Client goes to Resource Server, gets 403, 403 points to Authorization Server
  Client goes to Authorization Server, gets 403, 403 points to Resource Owner
  Client goes to Resource Owner, gets Access Token A (an Access Grant).
  Client takes Access Token A to Authorization Server with Access Token A, gets Access Token B
  Client takes Access Token B to Resource Server, gets resource

You could see how this could recurse.  

(It also occurs that you could also parallelize the process too and require multiple Access Tokens.  The analogy would be something like: "you must first collect the sword of Knorr, the sceptre of Manoth and the crown of Zaxx before you can claim the throne of Orion".  I don't know if this fits into the existing model at all, or even if it makes sense at all.)

==Other Stuff: Crazy Out of Band Ideas

That's really the core of the idea that I had, but there are a few things that I think are worth considering.

Implicit in my description is the fact that the entity that demands an Access Token (Resource Server, Authz Server) needs to know about the entity that produces it (Authz Server, Resource Owner).  I don't think that this is an unreasonable constraint.  The consumer of the Access Token has to know who produced it to check whether or not the Token can be trusted enough.

That could be used to push a number of things out of scope for the specification.  (Again, with due caution regarding the security properties - it might still be necessary to describe these uses, even if the on-the-wire formats aren't defined.  On-the-wire formats might still be part of external work.)

A lot of specification goes into specifying the things that are being exchanged between Resource Server and Authorization Server.  These could almost all be deferred to other specifications or only talked about from the security perspective.

For instance, the current request for an Access Token includes a client_id.  The reason for this is to ensure that the Access Token is correctly bound to a specific Client - so that another client can't use the token [3].  When it is assumed that the Resource Owner knows about the Authz Server, we can also assume that the Resource Owner is able to direct the Client to the Authz Server so that the Access Token it produces has the necessary bindings.

Another example is the redirect.  If the client follows the link to the Authorization Server, a Referrer header might be observed by the Authorization Server.  Once the Authorization interaction is complete, the Authorization Server could redirect back to the URI it first saw.  Alternatively, this information might be buried in the URI with the other junk, as described above.  There's no need to formalize this mechanism if the agreement is private.

E.g.
  Client goes to Resource Server, gets 403 response.  The 403 contains a URI that, along with identifying the Authorization Server, includes in an opaque portion that contains additional information: the client identity, the resource the client is trying to access, and other constraints.  The Resource Server knows how to construct this URI so that the Authorization Server can make sense of this.  By navigating to this resource, the Client ultimately acquires an Access Token - probably some sort of document with a media type that identifies it as an Access Token.
  Client brings the Access Token back to the Resource Server.  The Resource Server cracks that token open and checks it: is it valid? is it from an Authorization Server that I trust? is it for the current resource? is it for the current requester? is it valid right now?  If a check fails, the request is rejected; if they all pass, the request is permitted.

Formalizing the exchange between Resource Server and Authorization Server lets an arbitrary Resource Server use an arbitrary Authorization Server.  There are good use cases for this, but they are secondary to the function I describe above.  Separating that makes a great deal of sense.

==Other Stuff: Slightly Wacky Ideas

What if the resource that provides the Access Token provided the Access Token to any and all who asked?  That's not unreasonable if the token can only be used by an authenticated Client.

If the resource didn't exist until the Client was authorized, then that would make this more effective.

Or, maybe better yet, the resource might exist anyway, but the Client isn't told about the resource until they are authorized.  Anyone that knows the URI can use it freely, but only the authorized are ever granted knowledge of the URI.  The only condition is that the URI is hard to guess.  (See RFC5808 for a different application of this concept and a discussion of the limitations.  I'm also aware that this might have other issues in a web environment, because URIs don't stay secret with Referrer headers and the like.)

In this model, the URI and Access Token are interchangeable.  If you have the URI, you can give it to someone and that's just as good as giving them a token.  This might be useful for some of the more extreme use cases.

==No-so-thin Client

A lot of focus has been placed on using a client that doesn't understand OAuth for each of the use cases.  The use of redirection, particularly with URI fragments, belies this.  None of what I've suggested is impossible, it just requires that the Client have some control over the content of the HTTP requests that it makes.

That doesn't require explicit support from an HTTP client, just the ability to make requests and set the content of headers.  Such support has been table stakes for a long time.

Regards,
Martin


[1] Note that authentication is not strictly necessary, it might be sufficient to provide evidence of authorization as described in the counter-examples here: http://arstechnica.com/security/guides/2010/09/twitter-a-case-study-on-how-to-do-oauth-wrong.ars/2

[2] Copious caveats - as I mentioned, treating the Access Token as an opaque blob is merely a useful abstraction.  It gets you a long way.  Before you have to worry about specifics you can understand a lot about the goals and how those goals are achieved.

[3] It's interesting that the Access Token producer doesn't need to validate the client identifier.  As long as they aren't duped about who they are producing the Access Token for, they don't care that the entity that is asking is the same or not - only the consumer of the token needs to check that it is OK.