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

Dick Hardt <> Thu, 09 July 2020 18:33 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id 706513A0DAA for <>; Thu, 9 Jul 2020 11:33:30 -0700 (PDT)
X-Virus-Scanned: amavisd-new at
X-Spam-Flag: NO
X-Spam-Score: -2.096
X-Spam-Status: No, score=-2.096 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, HTML_FONT_LOW_CONTRAST=0.001, HTML_MESSAGE=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Authentication-Results: (amavisd-new); dkim=pass (2048-bit key)
Received: from ([]) by localhost ( []) (amavisd-new, port 10024) with ESMTP id dCSt5pKBz3A6 for <>; Thu, 9 Jul 2020 11:33:26 -0700 (PDT)
Received: from ( [IPv6:2a00:1450:4864:20::129]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by (Postfix) with ESMTPS id 2C1E93A0DAC for <>; Thu, 9 Jul 2020 11:33:26 -0700 (PDT)
Received: by with SMTP id t9so1743874lfl.5 for <>; Thu, 09 Jul 2020 11:33:26 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=bhAvsyWjEKKquzbsXYDp5mDbAAe27EmjNAWGi6MmhrE=; b=vDk8jGxen2inYky1O2gUPJw8aCNniuZ1GqOZwvlhJB9cp/+ra/nAK0m6bVHdhhf6AG a6tvV7aEGu0Cx4ygnxRG3D7wn68z87xW14IJqHrqKqG1HhRwpA+00c8WfkM+Mt4506+K U7OIfBaFQTXbt19JMe2kNjRtLDtpyKU34apcDA2LtZekPI97nSQXnNUNeLErT9RnRv4H cj0pxdvj3y3Q9i6S6CVYXmn+7C884sbnPvr5g5xXo4z4zz+9KnfRUWYSLZS6ziCuwqME 6M/B9c04W0z3O1Af68pVRJO5LYC1WWciJ4067MnQHNEK+qW1JgBcdhQSgHKZL0rdaCb/ V0Mw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=bhAvsyWjEKKquzbsXYDp5mDbAAe27EmjNAWGi6MmhrE=; b=AnHVvlGWMFxusq1v+qDwbRxgbb5+EtqJDoq8o1vyG+2d6QBV3aol+mEfLWCCkPrHiM b73UL4z3+dDNehZN6Skq/HQYEWloD7NjXNi7vnUMTEgQFRgMl7J/demKhGahdFG5Irjv 1UZaHNWSArreUi2ZBbqFyDUs5uRSWw2XMhWA/2LKqkBZXv4qFlTZR1qVBYoeQ0qkLpQm 7JGQ1Sbgu9anOjsvLqXIdKXKt4K9CLCqstC+EDu7Vbp1szIfQmfANACSUERnBb0Vt+WW U2CWX1KP+cilPb8W9XXadGUR61jRdfu812SZiokLeZ6gWtgwpa8FimUSJcyD7qS9iwwM kc+A==
X-Gm-Message-State: AOAM530TakUT7KUwLRR1lAUNsiN7IX/0MVgmlTa7ZkH0PMUTixdJ2wTu a2Se2/XfC+KTR6yQUccmtGdc4NlBCZLlZX1ugno=
X-Google-Smtp-Source: ABdhPJzoVDilGg1JEucjN3XOcD9GK/C0zmMZMU5NiTpzXuFlvtW9iFyP18qyA10r7F6yoNc9s8YoWP3iW3fDNW9MAbE=
X-Received: by 2002:a19:4143:: with SMTP id o64mr40348459lfa.157.1594319604127; Thu, 09 Jul 2020 11:33:24 -0700 (PDT)
MIME-Version: 1.0
References: <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>
In-Reply-To: <>
From: Dick Hardt <>
Date: Thu, 9 Jul 2020 11:32:47 -0700
Message-ID: <>
To: Justin Richer <>
Content-Type: multipart/alternative; boundary="000000000000a99ddd05aa06754a"
Archived-At: <>
Subject: Re: [Txauth] Polymorphism (Was: JSON Schema?)
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: <>
List-Unsubscribe: <>, <>
List-Archive: <>
List-Post: <>
List-Help: <>
List-Subscribe: <>, <>
X-List-Received-Date: Thu, 09 Jul 2020 18:33:31 -0000

Looks like you missed the point of my code example, as your response is
focussed on the aspects I had in comments, so let me clarify.

This line (which is determining the type of the item in the array):

if (typeof item === string')

is implicitly stating that the item is an oauth scope. Whereas it is
explicit in this statement

if (authorizations.type === 'oauth_scope')

which I think it easier to understand what is happening (in my opinion of

XYZ has types, they are just implicit. RAR has explicit types, and that
does not look to be holding back RAR. I don't understand why you think
having explicit types will hold us back. Do you want to let the string and
object be anything the AS and RS decide they could mean? That would make
GNAP more of a framework than a protocol, and a key aspect of the request
would be undefined.

My thoughts have not shifted on "types" in interactions. I just changed the
name to 'mode'. I did shift my thinking that a negotiation of interaction
modes is useful, and added that.

We still have an unfinished discussion there. When you have a chance, would
you respond to that thread?


On Thu, Jul 9, 2020 at 8:39 AM Justin Richer <> wrote:

> Declarations of which code is “easier to understand” are going to be
> subjective, and I don’t agree with your conclusions even with your
> examples. Even so, I don’t think that the examples you give are a good
> comparison. While it is of course possible to implement things like you
> have below, I think it’s indicative of thinking of things in terms of a
> “resource request type”. What I’ve been trying to argue is that there
> shouldn’t be a “type”, that there should just be slightly different ways to
> do a resource request. So the code is more like:
> resourcesRequested = item => {
>    if (typeof item == ‘object’) {
>       return new RarStyleResourceRequest(item);
>    } else if (typeof item == “string”) {
>      return new StringStyleResourceRequest(item);
>    }
> });
> processResources(resourcesRequested);
> Each of these “*ResourceRequest” objects would be something that the AS
> can use to decide what to put into the access token. Although you’d
> probably put that complexity into a factory constructor or something
> instead of a map function, this shows the kind of dispatch you can do based
> on type if you’re doing it by hand. You collect everything in the array,
> and turn it into an object that represents “a request for a resource that
> gets tied to an access token”. And then you process the request based on
> the collection of resource requests. Each string-based request points to
> some set of policies, as does each object-based request. You can even
> imagine that instead of creating a separate string-based request, you use
> that string to look up a policy in a policy engine to be applied later.
> The AS then has to figure out, as it always does, what to do with this
> collection. And if you want to do an early escape on object style requests,
> you could just throw an error when you detect that. Or, you just ignore
> that part of the request. In fact, here’s the code I wrote that handles it
> exactly that way, while processing the JSON by hand in Java on a legacy
> OAuth 2 server:
> JsonArray resources = json.get("resources").getAsJsonArray();
> Set<String> scopes =, false)
> .filter( e -> e.isJsonPrimitive() ) // filter out anything that's not a
> handle
> .map( e -> e.getAsString() )
> .collect(Collectors.toSet());
> tx.setScope(scopes);
> Furthermore, your XAuth example doesn’t go to the same depth as the XYZ
> example, which leads to a false comparison. In your “process scope” you
> would need to parse the “scope” string to split on spaces, and then have
> another loop to process each scope. For the “process details” you’d need to
> iterate over the array at the root of a RAR-style request and process each
> piece. In the XYZ code, you’ve got all of that already. If you’re going to
> compare the complexity of code, they should at least be shown to the same
> point of the process.
> On top of that, the fall-through case statement below is really limiting.
> What if the scope processing or RAR objects processing gets combined with
> something else? This kind of premature optimization is not something we
> want to encourage developers to do.
> But ultimately, I think the disconnect is down to thinking about this in
> terms of an explicit “type”, much the way XAuth used to do with the
> interactions. I’m glad that your thoughts have shifted in that space, and I
> think you should strongly consider the same set of arguments here. A lot of
> the promise of GNAP is getting away from this type-field style design, like
> getting away from OAuth 2’s “grant_type” approach and into something that’s
> focused on interaction and client models instead. This newer model allows
> for better flexibility, better consistency, and better clarity throughout.
> It’s no longer “if I see this flag then I go down this separate code path
> and nothing else”, it’s now “if I see this item, I go down this code path
> and then process the next item too”. There’s power in the combinations.
>  — Justin
> On Jul 8, 2020, at 9:31 PM, Dick Hardt <> wrote:
> I had intended to provide a code sample comparing XYZ request type with
> XAuth request type
> Let's say an AS only supports OAuth scopes (does not support RAR). Here is
> JS code to check:
> //XYZ code
> var oauth_scope = true;
> requests.forEach( item => {
>     if (typeof item === "object")
>         oauth_scope = false;
> });
> if (!'oauth_scope') {
>     // return error
> }
> // XAuth code
> if ('oauth_scope' != authorizations.type) {
>    // return error
> }
> Here is some JS code that for an AS that supports OAuth scopes, RAR, and
> OIDC requests:
> // XYZ
> if (request) {
>     requests.forEach( item => {
>         if (typeof item === "object") {
>             // process a RAR item
>         } else if(typeof item === 'string') {
>             // process a scope item
>         } else {
>             // throw an error
>         }
>     })
>    // process the whole request
> }
> if (oidc_claims_query) {
>     // process oidc request
> }
> //XAuth
> if (authorizations) {
>     switch (authorizations?.type) {
>         case 'oauth_rar':
>             // process authorizations.details - RAR
>         case 'oauth_scope':
>             // process authorizations.scope
>             // process the whole request
>             break;
>         case 'oidc':
>             // process OIDC claims
>             break;
>         default:
>             // error for unknown type
>     }
> }
> Understanding what the code is doing looks much clearer in XAuth to me.
> ᐧ
> On Wed, Jul 8, 2020 at 3:55 PM Dick Hardt <> 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 <> 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 <> wrote:
>>> On Wed, Jul 8, 2020 at 1:02 PM Justin Richer <> wrote:
>>>> On Jul 8, 2020, at 3:16 PM, Dick Hardt <> 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]
>>> ᐧ
>>> ᐧ
> ᐧ