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

Dick Hardt <> Thu, 09 July 2020 22:51 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id D0F7E3A095B for <>; Thu, 9 Jul 2020 15:51:12 -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 SFvwAXE3iGnD for <>; Thu, 9 Jul 2020 15:51:08 -0700 (PDT)
Received: from ( [IPv6:2a00:1450:4864:20::229]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by (Postfix) with ESMTPS id EA01F3A094F for <>; Thu, 9 Jul 2020 15:51:07 -0700 (PDT)
Received: by with SMTP id h22so4225132lji.9 for <>; Thu, 09 Jul 2020 15:51:07 -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=i55VCcK9H29QOUtwsJQLjc6Qv0lyhntErNpca2ARBSs=; b=L/0vUPl9gmoIX2aU+ZNfItVQ+LsyyJCtF76naYwBJM5yDhMCx6QG0N3JP/yUvEkYUU jhp676fSUbp6GRKXxwvDK9vlCqCpO7BEn0JvDXOnEP09loOZAQDgY0m8h66r7gn+tfJ1 d6vVItVMBxyr/sL27LFal0qeY6kVuNoJyK7snPbPNKwS70uo16u99XuXVz4EGNuqp28Y Htw+Z9PlYhRgR2h8Uvz3dpnCCHkNZeBHbYDlpcBgJdfoZRrWMQHbbH1FJT/PiGDiYpYt N1n3hXdKVtghYBJKii1h404k82v2mOolnScUOEIrpS0qmbY9AjvFoMu3V2UNmOh+AL+Z BWoA==
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=i55VCcK9H29QOUtwsJQLjc6Qv0lyhntErNpca2ARBSs=; b=G90aDfPMzMg0q1Sgc/OQXCQGKFsrGOq+/OZ9XZFQEAvKT7yfVnkSMwHrs4Du5hlAxR rVXRTFOebjMARI+xBw0Kh+TeRq9Jye5y59dP7uGugkoqiwa7PphHawBE8HNc+HV/pZ/O h94DIIJaW4ctmXgUwE6dgYAl06GZ27V4BtGU6ZA58GacK7SvVPoIoKEf7rKcmGmLoKjs aT27DrxjKdiHilMZuOS67P+lnThMBxl4ytsMxLeyQdJqubVLH/C0zdjnU6JAP3UkQL8I NjF8yftJ/V7Roz4MUKhObcKzw70o25asv77bc9xaZHDmZiXPDuDIatWNKvWsdI7S0rdn pjfQ==
X-Gm-Message-State: AOAM5318jRPBEZTRgNYb9D54QTxNERh6b3bAmoCipVLJ5nL9JF3aMHJd a8HZ15TKr0sMgAQtwwRLXitkM8dz9W8TJOF98XM=
X-Google-Smtp-Source: ABdhPJx5h3eOrNrkZudwsJ+M0qtyLBUsEA4s6n7e68dy1pGgi/RsncTd0dlxtMWNtNduzEbw7gFzF9fqLrYzsCbAg3g=
X-Received: by 2002:a2e:900a:: with SMTP id h10mr39978239ljg.242.1594335065810; Thu, 09 Jul 2020 15:51:05 -0700 (PDT)
MIME-Version: 1.0
References: <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <>
In-Reply-To: <>
From: Dick Hardt <>
Date: Thu, 9 Jul 2020 15:50:29 -0700
Message-ID: <>
To: Justin Richer <>
Content-Type: multipart/alternative; boundary="000000000000402bc505aa0a0f61"
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 22:51:13 -0000

On Thu, Jul 9, 2020 at 12:17 PM Justin Richer <> wrote:

> On Jul 9, 2020, at 2:32 PM, Dick Hardt <> wrote:
> 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.
> The point seemed to be about the overall complexity and readability, and
> that’s what I responded to.

It was about the complexity and readability -- of those specific lines of

I've used JS type polymorphism to provide a cleaner API. For example,
making an HTTP request. If I'm good with all the defaults, I can just
provide the URI as a string, if I want to change a default, I provide an
object and the URI is now a property of the object. Polymorphism allows the
caller to have a simpler interface when it is a simple operation.

Do you have any examples of JSON polymorphism used in other protocols
besides in a JWT?

> 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.
> No, it is not. It is saying that it is a resource request represented as a
> string. One way to represent a resource request as a string is an OAuth 2
> style scope. And if you’re building up from an existing OAuth 2 system,
> then you could use a scope there. But that’s not the same as stating “the
> item is a scope”. In my examples, I had been trying to use the terminology
> that you were using, but I’m afraid that made things more unclear.

We are comparing how to do it in XYZ vs XAuth -- so to make it apples and
apples, the string is a scope, since that is the use case we are looking at.

> 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
> course).
> 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.
> The “type” in RAR is a name spacing device to allow extensibility in
> different aspects of the request. This is at a lower layer than how they’re
> being applied here, and it therefore makes more sense at that layer. It’s a
> way for a particular API to define the dimensions that it cares about in
> its request. It’s not really an even comparison.

And the type in XAuth authorizations is different schemas for making a
request. "oauth_scope", "oauth_rar", and now "oidc". I would expect that
there may be more in the future, and we have a clear way of adding schemas,
and for the client and AS to know they are talking the same "language".

> Do you want to let the string and object be anything the AS and RS decide
> they could mean?
> Yes. Just like the AS can decide that an OAuth scope could mean any number
> of things.

That was what I was afraid of. While an OAuth scope could mean whatever the
AS decides it wants to be, the Client and AS know it is an OAuth scope.

> That would make GNAP more of a framework than a protocol, and a key aspect
> of the request would be undefined.
> I don’t see how this would make things a framework and not a protocol.
> We’re not debating the ability to send a set of strings or a set of objects
> to the AS, which the AS will interpret in the request. We’re debating the
> syntax for sending those values.

> 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.
> The important shift is the removal of a single “type” field that meant you
> could only send one thing at a time, and now you can send multiple things
> in various combinations as need allows. That’s the kind of thing that I’m
> saying is problematic here as well, for many of the same reasons.
> We still have an unfinished discussion there. When you have a chance,
> would you respond to that thread?
> I believe the thread you’re referring to was commented on by several
> people, including an AD, as not being a helpful conversation for the group.
> I stopped responding to that thread when that was brought up, in order to
> respect that position and hopefully move the conversation along.

This is the thread I am referring to:

And I don't think Ben was saying the conversation was not helpful, but
wanted to clarify the goal:

> I’m beginning to think that this conversation thread is itself going off
> the rails in a similar way, since we seem to be repeating things at each
> other.

Seems later messages in this thread indicate otherwise as there seems to be
some additional understanding.

> It’s become a comparison of solutions in detail and not a discussion of
> what the driving use cases are. I understand that you think your proposal
> is better, and I disagree and have pointed out what I see as several major
> flaws in it.

Would you list those for me so I have a better idea of what they are? What
aspect of the XAuth authorizations could you not live with?

> My goal here isn’t to convince you as an individual that I’m right. My
> goal here is to present my ideas and explain the thought process behind
> them so that we all, as a group, can make the best decisions going forward
> based on that.

Well it seems we have different goals. I'm trying to get a better
understanding of your proposals, explain my concerns, and offer a solution
to arrive at some rough consensus. You only want to present your ideas.


>  — Justin
> /Dick
> 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]
>>>> ᐧ
>>>> ᐧ
>> ᐧ