[Openid-specs-ab] OpenID Connect Key Binding vs OpenID Connect UserInfo Verifiable Credentials
Dick Hardt
dick.hardt at gmail.com
Tue Sep 9 16:54:09 UTC 2025
I agree there is potential for RP1 to share info it should not share with
RP2
It is unclear that the extra complexity of asking for a new token is
going to prevent that any better than an OP deciding it won't issue an
id_token with key binding. IE the OP making a decision about what is in an
id_token and not in another token seems complicated for most OPs.
More importantly to me, I am concerned about the lift to educate
implementers about a new token and a new way to ask for it. I suspect the
lack of interest in the OpenID Connect UserInfo Verifiable Credentials is
due to the complexity.
I also don't think we are overloading the id_token -- but we can agree to
differ in our opinion on that.
On Tue, Sep 9, 2025 at 1:51 PM <george at practicalidentity.com> wrote:
> Hi Dick,
>
> Thanks for answering my question (again) :)
>
> I don’t feel like the argument of “the OP can’t prevent the RP from doing
> X” is a viable one for specifications. The spec should
> encourage/push/require the RP to “do the right thing”. I believe the
> current model makes is easy for the RP to “do the WRONG thing”.
>
> Use cases where the id_token flows across trust boundaries make no sense.
> The ’sub’ claim, in many cases, has no relevance in the second trust domain
> as the identifier for the user is different (GUID in trust domain A does
> NOT equal the user in trust domain B). This is why the identity chaining
> spec leveraged the JSON Authorization Grant to allow the AS in trust domain
> A to correctly identify the user in trust domain B.
>
> Also, trying to leverage user consent as a way to solve this is really a
> “user experience dark pattern”. What the user is trying to do will NOT work
> if they don’t consent. What if the RP requesting the id_token also needs to
> know some personal health information and wants that in the id_token. Now
> the user is sharing that data with whoever the RP decides? Or has to make
> the choice between sharing or reduced functionality?
>
> I’m still confused why a dedicated token for authentication/status is not
> a viable option? If the RP is a web based RP, then requesting this token
> from the AS either during the authentication front channel flow, or via a
> backchannel flow like ID-JAG shouldn’t be an issue. For me, just because
> the RP is already getting the id_token doesn’t mean we should overload the
> id_token semantic.
>
> Thanks,
> George
>
> George Fletcher
> Identity Standards Architect
> Practical Identity LLC
>
>
>
> On Sep 9, 2025, at 1:16 AM, Dick Hardt <dick.hardt at gmail.com> wrote:
>
> Hi George
>
> My answer to your question:
>
> If RP1 requests claims that MUST NOT be sent to RP2, then what is the OP
> supposed to do? Reject the request? Send back an id_token that is
> insufficient for the client (RP1) but good for RP2? Send back an id_token
> that works for both the client and RP2 which has leaked privacy claims?
>
>
> Is that the OP can't prevent RP1 from sending whatever it wants to RP2. If
> the OP does not trust RP1 to do the right thing, then perhaps the OP should
> not issue an id_token to RP1?
>
> I have added the following content to the Privacy Considerations and added
> you to the acknowledgements:
>
> > Public key bound ID Tokens will often contain personal (PII). The RP
> SHOULD obtain user consent before sharing a Public key bound ID Token that
> contains PII with a third party.
>
> On Tue, Sep 9, 2025 at 1:57 AM <george at practicalidentity.com> wrote:
>
>> Ok, let me restate. RP1 is an OpenID Connect Relying Party acting as an
>> OAuth 2.0 client. This is exactly how OpenID Connect defines the Relying
>> Party term.
>>
>> Relying Party (RP)OAuth 2.0 Client application requiring End-User
>> Authentication and Claims from an OpenID Provider.
>> The point is not what RP1 can or cannot do with data it receives. Just
>> because RP1 CAN send the id_token to another party doesn’t mean is SHOULD.
>> The point is to define a mechanism that maximizes the likelihood RP1 will
>> “do the right thing”. Which from what I understand, is that RP1 needs to
>> communicate information about the authentication status/context with
>> another entity. This information needs to be authorized by the OP and MUST
>> NOT leak PII about the subject that the receiving entity should not receive.
>>
>> Also, I didn’t see an answer to my question…
>>
>> If RP1 requests claims that MUST NOT be sent to RP2, then what is the OP
>> supposed to do? Reject the request? Send back an id_token that is
>> insufficient for the client (RP1) but good for RP2? Send back an id_token
>> that works for both the client and RP2 which has leaked privacy claims?
>>
>>
>> George Fletcher
>> Identity Standards Architect
>> Practical Identity LLC
>>
>>
>>
>> On Sep 8, 2025, at 7:27 AM, Dick Hardt <dick.hardt at gmail.com> wrote:
>>
>> > Finally, to Dick’s points about RP1 (really the OAuth client) sending
>> it to RP2, I believe that creates a mutually exclusive decision for the OP.
>> If the client requests claims that MUST NOT be sent to RP2, then what is
>> the OP supposed to do? Reject the request? Send back an id_token that is
>> insufficient for the client (RP1) but good for RP2? Send back an id_token
>> that works for both the client and RP2 which has leaked privacy claims?
>>
>> RP1 is not an OAuth client -- it is an RP. It does not want
>> authorization, it wants authentication. The OP cannot prevent RP1 from
>> sending any information it wants to RP2 independent of it being in an
>> id_token or in another part of the communication -- what RP1 shares with
>> RP2 is going to be based on business decisions, not a technical barrier of
>> it not being in an id_token.
>>
>> On Mon, Sep 8, 2025 at 12:17 PM <george at practicalidentity.com> wrote:
>>
>>> I’m going to respond to a couple points from different emails here as
>>> it’s easier to do it in one post :)
>>>
>>> Dick asked why I didn’t object to ID-JAG as the ‘aud’ rule isn’t applied
>>> by the OP in that draft.
>>>
>>> For me, the reason is that back when we did OpenID Connect Core, there
>>> was the issue of the id_token_hint parameter which meant sending the
>>> id_token back to the issuer that issued it. We had the discussion at that
>>> time and the way I remember it, we all agreed that it was ok for the
>>> id_token to be sent back to its issuing OP. I believe Karl makes the same
>>> point. I don’t see any privacy risks here either as the OP put all the
>>> claims into the id_token.
>>>
>>> Now regarding the comment about the Native SSO spec, in that spec also,
>>> the id_token is ONLY sent to the OP that issued it. Sending the id_token to
>>> a different OP would be outside the intent of that spec. As for clients
>>> written by the same company sharing the id_token, you could say that is an
>>> abuse of the ’aud’ rules and in fact there has been push back against the
>>> Native SSO spec for it’s use of the id_token.
>>>
>>> Regarding the Kubctl use of the id_token, I worked with my colleague at
>>> Capital One to get this changed to using a JWT based access token as the
>>> purpose of the token in the K8 use case was for authorization NOT
>>> authentication context.
>>>
>>> Finally, to Dick’s points about RP1 (really the OAuth client) sending it
>>> to RP2, I believe that creates a mutually exclusive decision for the OP. If
>>> the client requests claims that MUST NOT be sent to RP2, then what is the
>>> OP supposed to do? Reject the request? Send back an id_token that is
>>> insufficient for the client (RP1) but good for RP2? Send back an id_token
>>> that works for both the client and RP2 which has leaked privacy claims?
>>>
>>> I still believe the best path forward is a dedicated token for this
>>> purpose. The ID-JAG model is fine, send the id_token back to the OP to
>>> request an authentication assertion for RP2 and get back the token to
>>> share. If there is concern that the OP then knows who the client is
>>> communicating with, then make the authentication assertion usable for
>>> everyone (though I daresay many enterprise IDPs would not want this
>>> feature).
>>>
>>> Future responses today will be limited. I’ll check back this evening or
>>> tomorrow morning :)
>>>
>>> George Fletcher
>>> Identity Standards Architect
>>> Practical Identity LLC
>>>
>>>
>>>
>>> On Sep 7, 2025, at 5:07 PM, Karl McGuinness <me at karlmcguinness.com>
>>> wrote:
>>>
>>> There are several widely deployed infrastructure use cases that already
>>> take an id_token as a bootstrap credential
>>>
>>> -
>>> https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens
>>> -
>>> https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
>>> -
>>> https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers
>>>
>>> It's common for a CLI tool for example to to use something like device
>>> authorization grant to obtain an id_token from an enterprise IdP end
>>> exchange the the id_token for a backend session with a control plane. In
>>> fact Okta uses OpenID Native SSO
>>> https://openid.net/specs/openid-connect-native-sso-1_0.html for its AWS
>>> CLI implementation to SSO to different AWS Accounts. These use cases are
>>> attempting to use the ID Token similar to a X.509 as would benefit from a
>>> key binding for an ID Token IMHO. From a deployment perspective it's so
>>> much easier to get an ID Token from an Enterprise IdP than an X.509 for
>>> these scenarios. The security benefits of supporting dynamic issued
>>> credentials from an IdP that can implement zero-trust security controls
>>> including MFA outweighs the privacy risks.
>>>
>>> There are also cross-client identity scenarios
>>> https://developers.google.com/identity/protocols/oauth2/cross-client-identity
>>> where a native app for example may have its own identity and the backend a
>>> different client identity but to the end-user they are the same client.
>>> These deployments are a single trust domain and don't have the same
>>> privacy risks as cross-domain deployments
>>>
>>> With ID-JAG the id_token issuer is the processor of the token exchange
>>> request. The assertion is something it issued so there wasn't a concern
>>> for the same party to also validate it. We didn't want the IdP to have to
>>> maintain the state with a refresh token so decided to use the serialized
>>> authentication context in the id_token for the request as the goal was a
>>> chained authentication. There is also interest to flow these claims also
>>> in the ID-JAG to downstream AS. I could see ID-JAG also supporting a key
>>> binding for the id_token if supported by the IdP.
>>>
>>> -Karl
>>>
>>>
>>>
>>> On Sun, Sep 7, 2025 at 10:19 AM Dick Hardt via Openid-specs-ab <
>>> openid-specs-ab at lists.openid.net> wrote:
>>>
>>>> Hey George
>>>>
>>>> I see you (and many others) were supportive for adoption of the
>>>> Identity Assertion Authorization Grant
>>>>
>>>>
>>>> https://datatracker.ietf.org/doc/draft-parecki-oauth-identity-assertion-authz-grant/
>>>>
>>>> Where a client presents the id_token back to the OP/AS to get an ID-JAG
>>>> token. Clearly the "aud" in the id_token is not the OP / AS. If presenting
>>>> a token to a party that is not the "aud" is problematic, why did you not
>>>> push back on that proposal? The AS could return a special token that the
>>>> client could present back later to the OP to get an ID-JAG according to
>>>> your logic below.
>>>>
>>>> I think some of the difference of opinion here is evident in how you
>>>> are describing what is happening. You are using the terms client and AS for
>>>> the parties, which are OAuth / authorization terms -- and suggesting the
>>>> id_token is being presented to an RS.
>>>>
>>>> I would agree that using the id_token for authorization is problematic
>>>> -- but that is not what we are proposing.
>>>>
>>>> The RP1 gets an id_token that has a bound key from the OP.
>>>>
>>>> RP1 then presents the id_token to RP2, with some proof of possession
>>>> mechanism specific to the RP1 -> RP2 protocol.
>>>>
>>>> All the claims in the id_token are expected to be relevant in the
>>>> presentation to RP2.
>>>>
>>>> As Jacob notes -- the "auth_time", "acr", and "amr" claims could be
>>>> very useful to RP2.
>>>>
>>>> In other words, RP1 -> RP2 is a chained authentication event.
>>>>
>>>> /Dick
>>>>
>>>>
>>>> On Sat, Sep 6, 2025 at 4:02 PM <george at practicalidentity.com> wrote:
>>>>
>>>>> So I think there are a couple of things I’d like to add to this
>>>>> conversation.
>>>>>
>>>>> 1. The original intent/purpose of the id_token is to communicate an
>>>>> identity assertion to the requesting client about the logged in user. Hence
>>>>> the ‘aud’ claim in the id_token being the client_id of the requesting
>>>>> client. General JWT based ‘aud’ claim processing rules require another
>>>>> party receiving the id_token to reject it because the ‘aud’ claim doesn’t
>>>>> match the receiver (see section 4.1.3 of RFC 7519). I have significant
>>>>> concerns around trying to re-purpose the id_token for something different
>>>>> (communicating status of the authentication to another party than the
>>>>> requesting client).
>>>>>
>>>>> 2. The need for a secure way for the client to communicate to another
>>>>> party (e.g. resource server) status about the authenticated user is valid.
>>>>> However, I don’t believe that using the id_token is the best way to do
>>>>> this. That is because, in addition to the ‘aud’ claim issues highlighted
>>>>> previously, there are significant privacy risks in doing so. I’ve seen many
>>>>> deployments that put user claims into the id_token that are NOT appropriate
>>>>> for a subsequent resource server. Asking the AS to limit the user claims in
>>>>> the id_token because it will be used to communicate authentication status
>>>>> to a resource server could hamper the requesting client as it now needs a
>>>>> different way to get those user claims that are appropriate just for
>>>>> itself. I would much prefer that a new token be defined for the explicit
>>>>> purpose of communicating authentication status. That frees up the AS to
>>>>> limit PII related claims in that token while providing them in the id_token
>>>>> to the requesting client. This also allows the AS to identify the list of
>>>>> ‘aud’ values matching the receiving servers or potentially removing it
>>>>> completely.
>>>>>
>>>>> 3. A new ’scope’, as currently proposed, can instruct the AS to return
>>>>> this new token. There is much precedent for scopes to trigger this type of
>>>>> behavior including the ‘openid’ scope itself.
>>>>>
>>>>> In my career I have rarely seen overloading intent to work out well.
>>>>> Maybe others have a different experience.
>>>>>
>>>>> Thanks,
>>>>> George
>>>>>
>>>>> George Fletcher
>>>>> Identity Standards Architect
>>>>> Practical Identity LLC
>>>>>
>>>>>
>>>>>
>>>>> On Sep 5, 2025, at 5:18 AM, Jacob Ideskog via Openid-specs-ab <
>>>>> openid-specs-ab at lists.openid.net> wrote:
>>>>>
>>>>> Hi all,
>>>>>
>>>>> I'd like to chip in on the semantics of the two approaches.
>>>>>
>>>>> The way I've always interpreted the id_token vs the user info is the
>>>>> following:
>>>>>
>>>>> The *id_token* represents the authentication transaction that just
>>>>> took place. For that reason it contains important non-profile information
>>>>> such as auth_time, acr, amr etc which are useful for the RP when deciding
>>>>> if the authentication that took place is strong enough, fresh enough etc.
>>>>> It *can* also contain a number of useful claims about the account or
>>>>> the user but at a minimum it needs the subject in some form.
>>>>>
>>>>> The *user info *on the other hand is mainly governed by the profile
>>>>> scope and sibling scopes. It does not contain authentication specific
>>>>> information but rather only account specific details.
>>>>>
>>>>> In our implementation we tend to recommend adding account/user claims
>>>>> in the ID token if you think the RP needs them to save them the round trip
>>>>> for user info, but it's optional and up to the particular use-case. For
>>>>> instance if you intend to share the ID token as this spec proposes, then
>>>>> adding account claims should be weighed against the privacy posture
>>>>> required.
>>>>>
>>>>> That said, to me, issuing a proof based on user info is less valuable
>>>>> to a 3rd party as it would not contain the authentication specific details
>>>>> that may matter to that party as well. If nothing else, the auth_time is
>>>>> generally valuable. It could also convey details about how long the OP
>>>>> considers the session to be valid for if you chose to interpret the exp as
>>>>> such.
>>>>> Issuing a bound ID token seems more to the point if that's the main
>>>>> use-case we're after. If all we want to do is share user info details to
>>>>> another party then a credential would do.
>>>>>
>>>>> It sounds like they solve different problems and should not be mixed.
>>>>>
>>>>> Just my 2¢
>>>>>
>>>>> /Jacob Ideskog
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Thu, 21 Aug 2025 at 19:39, Dick Hardt via Openid-specs-ab <
>>>>> openid-specs-ab at lists.openid.net> wrote:
>>>>>
>>>>>> Here is my homework as assigned by the working group chair. :)
>>>>>>
>>>>>> KB = OpenID Connect Key Binding
>>>>>> UVC = OpenID Connect UserInfo Verifiable Credentials
>>>>>> Links to specs at bottom
>>>>>>
>>>>>>
>>>>>> *Tl;dr:*KB adds the key to an ID Token
>>>>>> UVC creates a verifiable credential with same info, but VC syntax
>>>>>> KB does it in one call to OP
>>>>>> UVC requires two calls to OP
>>>>>>
>>>>>> *Key Bound Token*
>>>>>> KB outputs an id_token that includes a `cnf` claim of the public key
>>>>>> UVC outputs a verifiable credential with a `did:jwk:ey...` claim
>>>>>> Both include all the same user claims
>>>>>>
>>>>>>
>>>>>> *Authentication Request*
>>>>>> - KB uses `dpop` scope as well as `dpop_jkt` parameter
>>>>>> - UVC uses `userinfo_credential`
>>>>>>
>>>>>> KB has extra layer of security as `dpop_jkt` provides additional
>>>>>> assurance between authentication request and token request
>>>>>>
>>>>>> *Token Request*
>>>>>> - KB - RP passes DPoP JWT as header
>>>>>> - UVC has no changes
>>>>>>
>>>>>> *Token Response*
>>>>>> - KB - OP passes back id_token that includes `cnf` claim
>>>>>> - UVC - OP passes back an access_token as well as c_nonce and
>>>>>> c_nonce_expires_in
>>>>>>
>>>>>> At this point, KB has completed the key binding ...
>>>>>>
>>>>>> *Credential Request and Response *
>>>>>> UVC continues on
>>>>>> - RP generates a verifiable credential request and passes it with the
>>>>>> access_token as a bearer token to the OP's credential endpoint
>>>>>> - OP returns a verifiable credential
>>>>>>
>>>>>>
>>>>>> https://dickhardt.github.io/openid-key-binding/main.html
>>>>>> https://github.com/dickhardt/openid-key-binding
>>>>>>
>>>>>> https://openid.net/specs/openid-connect-userinfo-vc-1_0.html
>>>>>>
>>>>>>
>>>>>> _______________________________________________
>>>>>> Openid-specs-ab mailing list
>>>>>> Openid-specs-ab at lists.openid.net
>>>>>> https://lists.openid.net/mailman/listinfo/openid-specs-ab
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Jacob Ideskog
>>>>> CTO
>>>>> Curity
>>>>> -------------------------------------------------------------------
>>>>> Sankt Göransgatan 66, Stockholm, Sweden
>>>>> M: +46 70-2233664
>>>>> j <jacob at twobo.com>acob at curity.io
>>>>> curity.io
>>>>> -------------------------------------------------------------------
>>>>> _______________________________________________
>>>>> Openid-specs-ab mailing list
>>>>> Openid-specs-ab at lists.openid.net
>>>>> https://lists.openid.net/mailman/listinfo/openid-specs-ab
>>>>>
>>>>>
>>>>> _______________________________________________
>>>> Openid-specs-ab mailing list
>>>> Openid-specs-ab at lists.openid.net
>>>> https://lists.openid.net/mailman/listinfo/openid-specs-ab
>>>>
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openid.net/pipermail/openid-specs-ab/attachments/20250909/64eede72/attachment-0001.htm>
More information about the Openid-specs-ab
mailing list