Re: [Txauth] Polymorphism (Was: JSON Schema?)

Justin Richer <jricher@mit.edu> Thu, 09 July 2020 19:21 UTC

Return-Path: <jricher@mit.edu>
X-Original-To: txauth@ietfa.amsl.com
Delivered-To: txauth@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id 712183A0E30 for <txauth@ietfa.amsl.com>; Thu, 9 Jul 2020 12:21:28 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -1.895
X-Spam-Level:
X-Spam-Status: No, score=-1.895 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, HTML_FONT_LOW_CONTRAST=0.001, HTML_MESSAGE=0.001, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id yjtjsjakL6aN for <txauth@ietfa.amsl.com>; Thu, 9 Jul 2020 12:21:24 -0700 (PDT)
Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 316973A0E2A for <txauth@ietf.org>; Thu, 9 Jul 2020 12:21:23 -0700 (PDT)
Received: from [192.168.1.7] (static-71-174-62-56.bstnma.fios.verizon.net [71.174.62.56]) (authenticated bits=0) (User authenticated as jricher@ATHENA.MIT.EDU) by outgoing.mit.edu (8.14.7/8.12.4) with ESMTP id 069JLL7H022078 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Thu, 9 Jul 2020 15:21:21 -0400
From: Justin Richer <jricher@mit.edu>
Message-Id: <6D58464F-3EFA-4367-9033-91FCB9CF40AC@mit.edu>
Content-Type: multipart/alternative; boundary="Apple-Mail=_8F1E4FA6-B835-4714-AF46-5E444B159EBB"
Mime-Version: 1.0 (Mac OS X Mail 13.4 \(3608.80.23.2.2\))
Date: Thu, 9 Jul 2020 15:21:21 -0400
In-Reply-To: <CAD9ie-tSjEWT-+D43yKaFZmEdFcbL=fyM0kHuFX_fNa4zHdm1w@mail.gmail.com>
Cc: txauth@ietf.org
To: Dick Hardt <dick.hardt@gmail.com>
References: <CAD9ie-vnA98pobbboS00SAHneEG52_8eMxh_sE3r3jg6gyooGg@mail.gmail.com> <E9EC90C9-7A9A-4909-8627-A161B33E941F@mit.edu> <CAD9ie-vyB8+5jS=K_qUHfvxsF2wPV5APRo+7WUDfJxNzJONJpg@mail.gmail.com> <8CC8B466-FD6F-4C23-8DAA-99B8A9BDF548@mit.edu> <CAD9ie-u9z7Mc-wNjztoOTy4N_Z9jFDc2Sb6quLspasMGAMKdSw@mail.gmail.com> <097FB93E-96DA-4DF6-8511-0B32FD321211@mit.edu> <CAD9ie-tpuisauOFGiUj65-RcYPtcvW_gZP1CAadqq5cE6P36HQ@mail.gmail.com> <EE4A7D91-1106-44CB-92BF-C3AA3649BDFE@mit.edu> <CAD9ie-saoc2FUm46r4h1B27iYK04j_skf5-zJR7EXLmWBzj=hA@mail.gmail.com> <F41A8F88-C1B4-4CE2-8573-7A03C086D25B@mit.edu> <CAD9ie-tHCg9Ti1xWuzUP5EGLAcU2cpFALErqq98+fPnD3enZCQ@mail.gmail.com> <820525FD-4556-4617-8D89-C600D8C90C33@mit.edu> <CAD9ie-uwz_Li7n9iuX_--YtWeE+HX5sWEe95nZ8Y0akYh8WTHg@mail.gmail.com> <A7E1F61B-78F8-4647-847A-E1C8909EA452@mit.edu> <CAD9ie-tSjEWT-+D43yKaFZmEdFcbL=fyM0kHuFX_fNa4zHdm1w@mail.gmail.com>
X-Mailer: Apple Mail (2.3608.80.23.2.2)
Archived-At: <https://mailarchive.ietf.org/arch/msg/txauth/buWyLWzRSZdDskMHATiqRj_RPEQ>
Subject: Re: [Txauth] Polymorphism (Was: JSON Schema?)
X-BeenThere: txauth@ietf.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <txauth.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/txauth>, <mailto:txauth-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/txauth/>
List-Post: <mailto:txauth@ietf.org>
List-Help: <mailto:txauth-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/txauth>, <mailto:txauth-request@ietf.org?subject=subscribe>
X-List-Received-Date: Thu, 09 Jul 2020 19:21:29 -0000

Ah, so you’re saying that the “userinfo” claims would be returned directly? I didn’t realize that you had intended to change how the OIDC claims query language functioned in your examples, and had assumed it would work the same was as the spec you were referencing. Your example of splitting it up makes that more clear. Though I would argue at that point, you’re creating a new query language since you’re not using the top-level functionality from ODIC’s definition.

 — Justin

> On Jul 9, 2020, at 3:15 PM, Dick Hardt <dick.hardt@gmail.com> wrote:
> 
> You are jumping to many conclusions below. Let me break down the proposal into some bite size chunks.
> 
> The developer would like to get some plain text OIDC claims about the user:
> 
>     "claims":{
>         "oidc": {
>             "userinfo" : {
>                 "name" : { "essential" : true },
>                 "photo" : { "essential" : false }
>             },
> 
> The developer would like to get an OIDC ID Token:
> 
>     "claims":{
>         "oidc": {
>             "id_token" : {
>                 "email"          : { "essential" : true },
>                 "email_verified" : { "essential" : true }
>             }
>     }
> 
> The developer would like to get an access token to acquire OIDC claims:
> 
> "authorizations": {
>         "type":"oidc",
>         "claims": ["name", "picture"]
>     }
> 
> The developer would like to get an access token to access photos:
> 
> "authorizations": {
>         "type":"oauth_scope",
>         "scope": "read_photos"*
>     }
> 
> The developer would like to get some VC claims:
> 
> "claims" {
>         "vc": {
>             "some_vc": "query_mechanism"
>         }
> }
> 
> The developer would like to do all of the above at once:
> 
> {
>     "authorizations": {
>         "userinfo": {
>             "type":"oidc",
>             "claims": ["name", "picture"]
>         },
>         "photos": {
>             "type":"oauth_scope",
>             "scope": "read_photos"
>         }
>     },
>     "claims":{
>         "oidc": {
>             "id_token" : {
>                 "email"          : { "essential" : true },
>                 "email_verified" : { "essential" : true }
>             },
>             "userinfo" : {
>                 "name" : { "essential" : true },
>                 "photo" : { "essential" : false }
>             }
>         },
>     "vc": {
>         "some_vc": "query_mechanism"
>         }   
>     }
> }
> 
> In the results, the developer gets claims back in the response claims object, and access tokens etc. back in the authorizations object. Just because an access token returns claims, it is still an access token.
> 
> Yes, the developer can't get a single access token that can both acquire OIDC claims, and access photos (there are separate tokens for each), but I would argue that separating the access is a positive security property, as a client component accessing the user profile has a token independent of the client component accessing photos. And at the AS, there is no requirement for the userinfo endpoint to take the same token as the photo endpoint.
> 
> Somewhat of a tangent, I'm thinking that 
> 
>     "claims":{
>         "oidc": {
>             "id_token" : {},
>             "userinfo" : {}
>         },
> 
> is a little verbose, and:
> 
>     "claims":{
>         "oidc_token": {},
>         "oidc_info": {}
>     },
> 
> works better and makes it easier to distinguish support for ID tokens vs plain claims.
> 
> wrt. my position on reusing OIDC -- it has not changed. I have viewed the OIDC claims as the "query language". No need to reinvent that. We are creating a new protocol, so no need to use the OAuth or OIDC protocols.
> 
> * OAuth scopes could be a space separates string to be consistent with OAuth 2, or an array of strings so that it is more JSON like. I have no strong opinion. 
> 
> scope.split(' ').forEach() 
> 
> is not that much more complex than 
> 
> scope.forEach()
> 
> 
> 
> On Thu, Jul 9, 2020 at 8:39 AM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
> But this approach doesn’t keep things in their respective containers — you’ve explicitly got “claims” underneath the “authorizations” header, and you’ve got items that come back as rights associated with the access token (which should be “authorizations”) in the “userinfo” section under the “claims” header. And as far as I can tell, these two sections are redundant to each other. Everything is everywhere. 
> 
> Additionally, this approach and syntax makes it difficult to combine different kinds of requests into one. One of OpenID Connect’s biggest draws, as I’m sure you recall, is that it could be combined with a request for non-OIDC resources. This was the real innovation that Twitter and Facebook’s identity APIs brought, access to more than just authentication, and what Google had tried to replicate with the awkward OpenID 2 + OAuth 1 hybrid system. Taking a step back from the existing solutions of OpenID 2 and OAuth 1 let us, as the community, see the value in the pattern that became OIDC on top of OAuth 2. 
> 
> Sure, you could say that the “oidc” type also can allow a “scopes” parameter, but what about a RAR style object, then? And what about when someone comes up with some new way to request access rights, or a VC-based query language? Does every “type” need to now know about every other “type” in order for an AS to be able to figure out how they go together? This seems like the protocol definition limiting what combinations the AS can handle, or what an RS might want to use.
> 
> My stance is that GNAP should have a way to query for rights in the access token (“authorizations” in xauth parlance) and identifiers for the user (“claims” in xauth parlance), and anything else should be an extension with potentially different models. The AS would process the “authorizations” equivalent (for the access token) alongside any other incoming query and then make a policy decision based on that.
> 
> I find it interesting that you are now saying we don’t need to use the OIDC request format when previously you’ve made it clear that you were in favor of pointing at external query languages, including their syntax. I’m glad to see that you’re now looking at things in a more flexible way, but I think it’s important that we be careful and conscientious about how we reference any external query languages in GNAP.
> 
>  — Justin
> 
>> On Jul 8, 2020, at 6:55 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>> 
>> Hey Justin,
>> 
>> Just because we are using OIDC claims, does not mean we need to mimic the OIDC request and response.
>> I was envisioning a grant request could look as the following (using XAuth syntax):
>> 
>> {
>>     "authorizations": {
>>         "type":"oidc",
>>         "claims": ["name", "picture"]
>>     },
>>     "claims":{
>>         "oidc": {
>>             "id_token" : {
>>                 "email"          : { "essential" : true },
>>                 "email_verified" : { "essential" : true }
>>             },
>>             "userinfo" : {
>>                 "name"           : { "essential" : true },
>>                 "picture"        : null
>>             }
>>         }
>>     }
>> }
>> 
>> Of course a developer could choose to only ask for a subset of this.
>> 
>> Note the new authorization type of "oidc", that takes "claims" rather than "scope". 
>> 
>> This keeps all the authorizations and claims in their respective request and response containers.
>> 
>> We had a thread months ago on the OIDC two stage model, and I still fail to see why forcing that has any advantage. 
>> 
>> /Dick
>> 
>> 
>> On Wed, Jul 8, 2020 at 3:25 PM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
>> I’m glad that we can agree that there are a number of things in legacy protocols that are unfortunate side effects of the circumstances in which they were built. Space-separated scope strings, for instance, would fall in that category, as I’ve previously explained.
>> 
>> A key point in the below: the OIDC “claims” request already mixes user claims (returned in an API) and authorization (to fetch user claims from an API), so that ship has sailed if you’re using it. It doesn’t make sense to have it under a “claims” or “authorizations” request, since it’s a query language that affects both. Maybe you’d call this another “unfortunate design”, but the context was about re-using an externally-defined query language that was not made for GNAP.
>> 
>> My scenario was for someone who is already using “claims” and wants to keep using it. (The vast majority of OIDC implementations, in my experience, don’t use that feature, and it’s not even required to be implemented by the AS, but again we’re talking about using this feature as an example of an external query language — and there are others out there.) In GNAP, you should return claims directly in the response, and request specific elements from the APIs protected by the access token. These are separate things, and by design both XAuth and XYZ have put them into separate containers in the request. This is a good design, and so putting something that conflates these two aspects into one or the other of the containers is not a particularly good option, in my opinion. 
>> 
>> Additionally, though this is a bit of an aside and getting into the specifics of identity, the “claims” parameter of ODIC is a query language bound to the full user profile. It is my stated position that the items coming back from the AS should only be identifiers, and not full profile information. The reasoning is pretty simple: the client doesn’t know what profile information it needs until it knows who the user is, so putting that into a protected API like the UserInfo Endpoint makes much more sense than putting it into the immediate response, where it is not needed, because the client already knows it. The AS doesn’t know what the client needs to know, either, so it can’t determine what to fulfill. OIDC’s two-stage model makes sense, and GNAP should really only focus on enabling this.
>> 
>>  — Justin
>> 
>>> On Jul 8, 2020, at 6:10 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>>> 
>>> 
>>> 
>>> On Wed, Jul 8, 2020 at 1:02 PM Justin Richer <jricher@mit.edu <mailto:jricher@mit.edu>> wrote:
>>> On Jul 8, 2020, at 3:16 PM, Dick Hardt <dick.hardt@gmail.com <mailto:dick.hardt@gmail.com>> wrote:
>>>> 
>>>> I think representing the request as an array is simplistic, and complicated at the same time.
>>>> 
>>>> On the simplistic front, as there is no clear mechanism for extending the request with properties that apply to all of the request. 
>>> 
>>> The elements of the array are taken as a set, to be tied to the same resulting access token. If one of those elements is defined, by the AS and/or the RS’s it’s protecting, to apply across some or all of the other elements, then that’s up to the AS’s policy. Much like the “openid” scope in OIDC, which switches on all sorts of contextual stuff in the request. So to handle something like this, an AS can easily declare that a given scope-style string or a given object property applies to different parts of the request. You don’t need to externalize it here.
>>> 
>>> I view the "openid" scope as an unfortunate design as OIDC was constrained by building on top of OAuth. (a problem I hoped to avert by having "identity" in scope for GNAP) The "openid" scope does not function as scope per se, and I think it makes OIDC harder to understand as the "openid" scope causes non-scope behavior. 
>>> 
>>>  
>>> 
>>> Do you have a concrete use case that requires that feature to be done in the way that you describe? I am trying to separate the driving use case from the proposed solutions to see what the differences are. 
>>> 
>>> Perhaps the client wants access to be HIPPA compliant? The HIPPA compliance signal applies to the scopes.
>>> 
>>> Adding other properties to an object is a well understood extension mechanism. Adding an additional element to an array that does not act like the other elements seems like a hack.
>>> 
>>>  
>>> 
>>>> 
>>>> Using JSON type polymorphism requires the AS to test each member of the array to determine if it is a string or an object. Only after detecting a RAR object does the AS know the client is making a RAR request. 
>>> 
>>> That’s correct — but the AS needs to parse the whole resources request in order to figure out what the client is asking for, anyway, whether it’s by strings or objects or whatever else might be defined by an extension. Is there an argument for having an AS do an early dispatch on a request before it’s fully parsed everything?
>>> 
>>> Let me clarify, the code is looking at the type of object that has been parsed. 
>>> 
>>> Determining if an item in an array is a scope or a RAR object by looking at the type being either a string or an object seems less clear than a property of an object explicitly declaring the type.
>>> 
>>>  
>>> 
>>>> This also limits the request to be composed only of scope strings or RAR objects. I don't see how other strings or objects could be used in the array, so there is no clear extension point in the "resources" array for other query mechanisms.
>>> 
>>> That’s not the case in XYZ since we aren’t declaring that a string has to be an OAuth2-style scope. It can be, but ultimately it’s just a string that the AS can understand. And the objects are just that — objects. If the AS understands what the object is, it can be a RAR object or it can be something completely API-specific with another query language entirely.
>>> 
>>> But the other query language would need a type that has been reserved in the RAR name space for there to be interop, so it effectively is a RAR extension.
>>> 
>>> There are query languages in other domains that may not fit nicely into RAR such as a query for medical records.
>>> 
>>> Yes, the medical records could be yet another top level property, but per below, I think that is confusing.
>>>  
>>> (Point, though: RAR already pretty much allows this by letting them be extended infinitely, a feature it inherits from XYZ)
>>> 
>>> I’m proposing that we do the same thing with GNAP: it’s an array of strings or objects and each of those means the same thing, “something the client is asking for”.
>>> 
>>>> 
>>>> Just as RAR has a "type" property, I propose the "resources" ("authorizations" in XAuth) be an object, where the other properties are determined by the "type" property. This allows extensions to define new ways to query for an authorization rather than having to fit into scopes or RAR.
>>>> 
>>> 
>>> It’s my stance that this is an unnecessary limitation at this level. The objects within the array should be typed, like RAR, but it doesn’t make sense for the overall request to be typed. Instead, there should be one “type" of query in the core, what XYZ calls the “resources” request. Other query languages should be added as extensions either to the RAR-style objects (by defining a type at that level) or as a separate top-level member.
>>> 
>>> For example, let’s take the OIDC “claims” query language. My current thought is that this really shouldn’t be a part of the “resources” or “claims” part of the request, but instead as its own top-level member, like it is in the OIDC protocol today. The main reason for this is the nature of the OIDC claims language: it specifies targets for the resulting data, and those targets cross different ways to return things. So it doesn’t actually affect just resources like the UserInfo Endpoint, or the ID Token, but both and potentially others out there. If your system supported such an extension, it could theoretically forego both the built-in “claims” and “resources” parts of the request, and use the “oidc_claims_query” member (or whatever it would be called). This would let such an extension use the OIDC claims processing mechanism as it is today.
>>> 
>>> To me, this remains much more understandable, extensible, and clean.
>>> 
>>> While this may be more understandable to a developer just porting an app OIDC that only wants OIDC results, but I think it is more complicated as soon as the developer wants other results, which is likely what prompted the developer to use GNAP instead of ODIC.
>>> 
>>> I think it is easier to understand if all the claims are in one container, and all the authorizations are in another container.
>>> 
>>> If a developer wants access to some resources, some claims, and an OpenID Token, they are having to use "claims", "resources", and "oidc_claims_query".  Now the claims and access tokens are spread across multiple containers. There are some claims in the "claims" container, and some "claims" in the "oidc_claims_query" container. And is the access token response in "oidc_claims_query" the same as an access token response in "resources"? It would seem simpler if they were, and if all the access tokens came back in the same container.
>>> 
>>> Per Aaron's post that you have referred to, the developer can get sme bare claims directly in the response in the "claims" object, an ID Token that has the same or different claims, and if they want, an access token that they can call a user_info endpoint to get the same, or different claims.
>>> 
>>> For example, an enterprise app client may want an ID Token with the email address, bare claims for the user's name and a URI to a profile photo, and an access token to query which groups a user belongs to.
>>> 
>>> We are still re-using the OIDC claims, but we are not mixing claims and authorizations.
>>> 
>>> /Dick
>>> 
>>> 
>>> [1] https://aaronparecki.com/2019/07/18/17/adding-identity-to-xyz <https://aaronparecki.com/2019/07/18/17/adding-identity-to-xyz>
>>> ᐧ
>> 
>> ᐧ
> 
> ᐧ