Re: RAR Structures for VC HTTP API

Oh, and an obvious dimension that I forgot to put into the examples below: locations! So you can talk about a token’s access rights as explicitly defined by a single endpoint, even if multiple endpoints are protected by the same AS. This allows you to do things like ask for one kind of access at one VC HTTP API endpoint and a different kind of access elsewhere:

[
{ 
 type: “vha”,
 actions: [ create ],
 datatypes: [ credentials ],
 locations: [ “https://example.com/vha/v1 <https://example.com/vha/v1>” ]
}, 
{ 
 type: “vha”,
 actions: [ derive ],
 datatypes: [ credentials ],
 locations: [ “https://personal-website.example/my-vha <https://personal-website.example/my-vha>” ]
}
]


Both of those rights get tied to a single token. Expressing something this simple is almost impossible to do with OAuth scopes, at least in a way that doesn’t leave massive security and interoperability holes in the wings.

 — Justin

> On Jul 21, 2021, at 10:40 AM, Justin Richer <jricher@mit.edu> wrote:
> 
> On yesterday’s call, we briefly discussed the applicability of RAR for the VC HTTP API, and I wanted to continue that conversation with some additional concrete points. Apologies in advance for the long email, but there’s a lot to untangle here as it seems a number of people have the wrong idea. 
> 
> First, what are we proposing? I am suggesting, strongly, that when defining this API, the group also define the different ways to control access to it. Which is to say, what actions can you do, what kinds of data do you want to switch on — in short, we’ve got an API, how do we want to slice it? I am explicitly saying that we do not define anything about the process of getting a token (so, specifying client credentials is a terrible idea) or how to process the token (so specifying token formats is also a terrible idea) because these don’t affect the API. What does affect the API is the availability of different kinds of actions for a specific call. What are the things that I can DO here?
> 
> This is what OAuth 2 invented scopes for, instead of the OAuth 1 or HTTP Basic method of “you just get everything”. So, why not just use parameterized scopes? It seemed like a good idea when I first invented it a decade ago: https://blue-button.github.io/blue-button-plus-pull/#scopes <https://blue-button.github.io/blue-button-plus-pull/#scopes> or when it got pulled into other efforts like https://openid.net/specs/openid-heart-fhir-oauth2-1_0-2017-05-31.html <https://openid.net/specs/openid-heart-fhir-oauth2-1_0-2017-05-31.html> … and Orie even suggested the following set of parameterized scopes for this API:
>             'create:credentials': Grants permission to create credentials
>             'derive:credentials': Grants permission to derive credentials
>             'create:presentations': Grants permission to create presentations
>             'verify:presentations': Grants permission to verify presentations
>             'exchange:presentations': Grants permission to exchange presentations   
> So what’s the problem? I can say with full confidence after years of experience building and deploying systems to support parameterized scopes like this that they are fragile, awkward, and lead to insecure corner cases. For example, am I supposed to parse these strings to find the bits on either side of that colon? Am I allowed to make up my own combinations of elements, so for example do I get “derive:presentations” from that set above, for free? What if I am protecting more than one API and they use a different separator? Or what if there’s just a plain old namespace collision and someone else has “exchange:presentations” on their unrelated API that that the AS is protecting at the same time? And what if someone has an additional dimension they need to convey, like the identifier for a specific account— does that mean we add a parameter to the string above, or do we need a separate scope to carry this, and how do we combine all of that? While all of these questions can be answered, the answers make something that seems simple on the surface spiral out of hand very quickly. I know this because I’ve seen it time and again, and this experience is exactly what led me to create what has become RAR. 
> 
> So let’s talk about how we’d address the above actions in RAR. First, please note that I’m going to be fast and loose with JSON syntax here because I’m typing it out by hand, so don’t try to compile this. :) Second, for simplicity of following this argument, let’s say that all five actions above are inside a single RAR type value, and we’ll just use “vha" as the stand-in for that value (in reality it would be a URI, and there might be multiple for a given API family like VC HTTP API). 
> 
> Picking apart the pieces above, we’ve got two elements that fit fairly well into the “actions” and “datatypes” fields in a RAR request type:
> actions: [ create, derive, verify, exchange ]
> datatypes: [ credentials, presentations ]
> Since RAR is about combining different dimensions into a single request, we can easily get to the kind of expressions above. Let’s say I want to create credentials and permissions, but nothing else — I can ask for the following value:
> 
> { 
>  type: “vha”,
>  actions: [ create ],
>  datatypes: [ credentials, presentations ]
> }
> 
> Or if I want to create and derive credentials but do nothing with presentations, I can do:
> 
> { 
>  type: “vha”,
>  actions: [ create, derive ],
>  datatypes: [ credentials ]
> }
> 
> And how about a more complex cross-product? Let’s say I want to create and derive credentials but only exchange presentations. All RAR requests are in an array, just like OAuth 2 scopes are fundamentally an array, so that request would look like this:
> 
> [
> { 
>  type: “vha”,
>  actions: [ create, derive ],
>  datatypes: [ credentials ]
> }, 
> { 
>  type: “vha”,
>  actions: [ exchange ],
>  datatypes: [ presentations ]
> }
> ]
> 
> Unlike with scopes, the extent of each aspect is clear to developers of both the API and the clients of the API. It’s very clear what I’m asking to do and what I’m doing it to, without having to define or parse or understand any kind of VC-HTTP-API-specific syntax to cram into an existing field. This gets even better with GNAP because you can ask for multiple different-scoped tokens at the same time.
> 
> And what if someone requests a confusing or bad combination?
> 
> { 
>  type: “vha”,
>  actions: [ create, derive ],
>  datatypes: [ credentials, presentations ]
> }
> 
> RAR clearly defines each object as the union of all items within it, so anything processing this would be able to tell that “derive” and “presentations” doesn’t fit together and this is an invalid request. There’s no need to guess and no need to put in special casing. Your implementation would be configured with viable combinations, and anything that falls outside of that is easily detected.
> 
> It gets even clearer if we want to talk about other dimensions not captured in the example scopes above. If I want to do this for a specific account? We can define a security identifier in this request to contain that information:
> 
> { 
>  type: “vha”,
>  actions: [ create, derive ],
>  datatypes: [ credentials ],
>  identifier: did:example:12345
> }
> 
> Already we’re doing something that would be difficult with scope strings.
> 
> And what if we are protecting multiple APIs that use similar syntax? They sit in separate namespaces so there is no chance for confusion.
> 
> [
> { 
>  type: “vha”,
>  actions: [ create, derive ],
>  datatypes: [ credentials ]
> }, 
> { 
>  type: “TOTALLY-NOT-VHA”,
>  actions: [ create, derive ],
>  datatypes: [ credentials ],
>  not-a-vha-attribute: do-some-weird-thing
> }
> ]
> 
> What does this mean for the VC HTTP API itself? It means that each call defined as part of the API would have a section like:
> 
>  Security:
>   Type: “vha”
>   Action: “create”
>   Datatype: “credentials"
> 
> … and that’s all the detail that would be needed. The core part of the spec would say something like “use the fields defined in the ’security’ section to make a RAR or GNAP request”, and that only applies if you’re doing OAuth 2 (with any flow) or GNAP (with any mode). You’re still free to protect the API with whatever fancy pants thing you want, but even then the Security section will lay out the core dimensions that each call is differentiated on.
> 
> So yes, it’s possible to pull this off with scope strings. I’ve done things more complex than this and have seen some really crazy stuff like using a fully-qualified query language as the scope string value. However, what I’m trying to convince this group is that there is a much simpler way and that’s by embracing the object structures that RAR lets you define, to talk about the dimensions of access that you want to allow or disallow. That discussion is going to happen anyway, and is probably ALREADY happening. I am saying this group should embrace that challenge as part of the protocol definition, and do so sooner rather than later. If we wait, it’ll end up a mess of proprietary solutions that don’t want to change, and people will dig in their heels even more for either a loose spec or patchwork of things so implementations don’t have to change. 
> 
> What I’m calling for is structure for interoperability and compatibility, in a way that’s a very, very light lift on the spec itself and would in fact help define the elements of the spec. Believe me — I’ve been down this road before and I am hoping that this group could learn from my mistakes instead of repeating them. 
> 
>  — Justin

Received on Wednesday, 21 July 2021 14:57:38 UTC