OpenID4VP icon indicating copy to clipboard operation
OpenID4VP copied to clipboard

Missing examples of presentation submission encoding

Open TimoGlastra opened this issue 2 years ago • 8 comments

The authorization response presentation_submission parameter has no examples in the spec of what an encoded version of it looks like.

This has resulted in some discussions around JFF Plugfest, and I think it could be helpful to add some references or examples of what an encoded presentation_submission looks like.

As I currently understand it, the following JSON:

{
   "id":"bbYJTQe7YPvVx-3rLl4Aq",
   "definition_id":"000fc41b-2859-4fc3-b797-510492a9479a",
   "descriptor_map":[
      {
         "id":"OpenBadgeCredential",
         "format":"jwt_vp",
         "path":"$",
         "path_nested":{
            "id":"OpenBadgeCredential",
            "format":"jwt_vc_json",
            "path":"$.vp.verifiableCredential[0]"
         }
      }
   ]
}

Should be encoded into the following parameter:

presentation_submission=id%3DbbYJTQe7YPvVx-3rLl4Aq%26definition_id%3D000fc41b-2859-4fc3-b797-510492a9479a%26descriptor_map%255B0%255D%255Bid%255D%3DOpenBadgeCredential%26descriptor_map%255B0%255D%255Bformat%255D%3Djwt_vp%26descriptor_map%255B0%255D%255Bpath%255D%3D%2524%26descriptor_map%255B0%255D%255Bpath_nested%255D%255Bid%255D%3DOpenBadgeCredential%26descriptor_map%255B0%255D%255Bpath_nested%255D%255Bformat%255D%3Djwt_vc_json%26descriptor_map%255B0%255D%255Bpath_nested%255D%255Bpath%255D%3D%2524.vp.verifiableCredential%255B0%255D

That is, you have to encode the presentation submission as a query string:

id=bbYJTQe7YPvVx-3rLl4Aq&definition_id=000fc41b-2859-4fc3-b797-510492a9479a&descriptor_map[0][id]=OpenBadgeCredential&descriptor_map[0][format]=jwt_vp&descriptor_map[0][path]=$&descriptor_map[0][path_nested][id]=OpenBadgeCredential&descriptor_map[0][path_nested][format]=jwt_vc_json&descriptor_map[0][path_nested][path]=$.vp.verifiableCredential[0]

You then make it url safe:

id%3DbbYJTQe7YPvVx-3rLl4Aq%26definition_id%3D000fc41b-2859-4fc3-b797-510492a9479a%26descriptor_map%5B0%5D%5Bid%5D%3DOpenBadgeCredential%26descriptor_map%5B0%5D%5Bformat%5D%3Djwt_vp%26descriptor_map%5B0%5D%5Bpath%5D%3D%24%26descriptor_map%5B0%5D%5Bpath_nested%5D%5Bid%5D%3DOpenBadgeCredential%26descriptor_map%5B0%5D%5Bpath_nested%5D%5Bformat%5D%3Djwt_vc_json%26descriptor_map%5B0%5D%5Bpath_nested%5D%5Bpath%5D%3D%24.vp.verifiableCredential%5B0%5D

After which you can assign it to the presentation_submission parameter.

If using the qs package from NPM, the code would look like:

qs.stringify({ 
  presentation_submission: qs.stringify({
	   "id":"bbYJTQe7YPvVx-3rLl4Aq",
	   "definition_id":"000fc41b-2859-4fc3-b797-510492a9479a",
	   "descriptor_map":[
	      {
	         "id":"OpenBadgeCredential",
	         "format":"jwt_vp",
	         "path":"$",
	         "path_nested":{
	            "id":"OpenBadgeCredential",
	            "format":"jwt_vc_json",
	            "path":"$.vp.verifiableCredential[0]"
	         }
	      }
	   ]
	})
})

Is this correct? I see the oid4vci spec uses JSON encoding for the credential offer, rather than encoding it as a query string (https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer). Is there a specific reason the presentation submission needs to be encoded as a query string?

TimoGlastra avatar Oct 03 '23 08:10 TimoGlastra

That is, you have to encode the presentation submission as a query string:

You then make it url safe:

You are likely to get into a mess if you view these are two separate steps. In particular it tends to fall apart if the key/value pairs in the query string contain an = or a %.

The best way to do this is with a proper url builder package. I'm not a javascript person but this package appears like it might be suitable:

https://www.npmjs.com/package/build-url

Is there a specific reason the presentation submission needs to be encoded as a query string?

I think it's because we're building on top of OAuth and the request to / response from the authorization server are [in base OAuth] passed in the query string.

jogu avatar Oct 03 '23 10:10 jogu

You are likely to get into a mess if you view these are two separate steps.

Don't pay too much attention to this part. I'm not very familiar with this process, but let's assume I'm using a library to take care of this.

I think what the main confusion is about is that it seems that there is only a single presentation_submission= parameter in all the (non-normative) examples. While if you were to encode the whole payload using an url-build / query string builder, it will have a lot of presenation_submission[id]=, etc.. parameters. So then the example is not a good reflection of what it would look like. Or may be that you first encode the presentation_submission, and then add the encoded to the presenation_submission query parameter. But if this is the case, I think that would warrant some explanation in the spec.

We've now also run into this with the vp_token property. If you provide multiple vps to the vp_token property, how should that be encoded? I assumed it would be adding multipe top level vp_token properties, with an index (vp_token[0]=ey...&vp_token[1]=ey...), but the organization that we're trying to get interop with read it as you have only one vp_token property (that is what the examples show) and thus you first need to encode the vp_token array and provide that as a single vp_token property.

The spec mentions it's either JSON string or an JSON Array, so I think it would also be valid to interpret it as that you can JSON stringify the JSON array and use that as the value for vp_token.

So I mainly want so guidance / clearance on what the expected payload looks like when using vp_token and presentation_submission, also taking into account multiple vps in the vp_token.

TimoGlastra avatar Oct 04 '23 08:10 TimoGlastra

For vp_token, https://github.com/openid/OpenID4VP/blob/main/openid-4-verifiable-presentations-1_0.md#response-parameters-response-parameters says:

vp_token: : REQUIRED. JSON String or JSON object that MUST contain a single Verifiable Presentation or an array of JSON Strings and JSON objects each of them containing a Verifiable Presentations.

I think that language could be improved, but I'm pretty sure it means "JSON array" when it says "array", and that is what the example at https://github.com/openid/OpenID4VP/blob/main/examples/response/vp_token_multiple_vps.json shows.

For presentation submission, the same section says:

presentation_submission: : REQUIRED. The presentation_submission element as defined in [@!DIF.PresentationExchange]. It contains mappings between the requested Verifiable Credentials and where to find them within the returned VP Token. This is expressed via elements in the descriptor_map array, known as Input Descriptor Mapping Objects. These objects contain a field called path, which, for this specification, MUST have the value $ (top level root path) when only one Verifiable Presentation is contained in the VP Token, and MUST have the value $[n] (indexed path from root) when there are multiple Verifiable Presentations, where n is the index to select. The path_nested object inside an Input Descriptor Mapping Object is used to describe how to find a returned Credential within a Verifiable Presentation, and the value of the path field in it will ultimately depend on the credential format. Non-normative examples can be found further in this section.

My reading of this is that there must only ever be a single presentation_submission, and that section describes how to write a single presentation_submission that goes along side multiple returned vp_token, and that is what you see in this example (which is the presentation submission for the above multiple vp token example):

https://github.com/openid/OpenID4VP/blob/main/examples/response/presentation_submission_multiple_vps.json

jogu avatar Oct 04 '23 12:10 jogu

For clarity, this would hence be my understanding of the correct response url for the above 2 examples:

https://client.example.org/cb#presentation_submission=%7B%22id%22%3A%22Presentation%20example%202%22%2C%22definition_id%22%3A%22Example%20with%20multiple%20VPs%22%2C%22descriptor_map%22%3A%5B%7B%22id%22%3A%22ID%20Card%20with%20constraints%22%2C%22format%22%3A%22ldp_vp%22%2C%22path%22%3A%22%24%5B0%5D%22%2C%22path_nested%22%3A%7B%22format%22%3A%22ldp_vc%22%2C%22path%22%3A%22%24%5B0%5D.verifiableCredential%5B0%5D%22%7D%7D%2C%7B%22id%22%3A%22Ontario%20Health%20Insurance%20Plan%22%2C%22format%22%3A%22jwt_vp_json%22%2C%22path%22%3A%22%24%5B1%5D%22%2C%22path_nested%22%3A%7B%22format%22%3A%22jwt_vc_json%22%2C%22path%22%3A%22%24%5B1%5D.vp.verifiableCredential%5B0%5D%22%7D%7D%5D%7D&vp_token=%5B%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%5D%2C%22type%22%3A%5B%22VerifiablePresentation%22%5D%2C%22verifiableCredential%22%3A%5B%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22id%22%3A%22https%3A%2F%2Fexample.com%2Fcredentials%2F1872%22%2C%22type%22%3A%5B%22VerifiableCredential%22%2C%22IDCardCredential%22%5D%2C%22issuer%22%3A%7B%22id%22%3A%22did%3Aexample%3Aissuer%22%7D%2C%22issuanceDate%22%3A%222010-01-01T19%3A23%3A24Z%22%2C%22credentialSubject%22%3A%7B%22given_name%22%3A%22Fredrik%22%2C%22family_name%22%3A%22Str%C3%B6mberg%22%2C%22birthdate%22%3A%221949-01-22%22%7D%2C%22proof%22%3A%7B%22type%22%3A%22Ed25519Signature2018%22%2C%22created%22%3A%222021-03-19T15%3A30%3A15Z%22%2C%22jws%22%3A%22eyJhb...IAoDA%22%2C%22proofPurpose%22%3A%22assertionMethod%22%2C%22verificationMethod%22%3A%22did%3Aexample%3Aissuer%23keys-1%22%7D%7D%5D%2C%22id%22%3A%22ebc6f1c2%22%2C%22holder%22%3A%22did%3Aexample%3Aholder%22%2C%22proof%22%3A%7B%22type%22%3A%22Ed25519Signature2018%22%2C%22created%22%3A%222021-03-19T15%3A30%3A15Z%22%2C%22challenge%22%3A%22n-0S6_WzA2Mj%22%2C%22domain%22%3A%22https%3A%2F%2Fclient.example.org%2Fcb%22%2C%22jws%22%3A%22eyJhb...JQdBw%22%2C%22proofPurpose%22%3A%22authentication%22%2C%22verificationMethod%22%3A%22did%3Aexample%3Aholder%23key-1%22%7D%7D%2C%22eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDpleGFtcGxlOjB4YWJjI2tleTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJqdGkiOiJ1cm46dXVpZDozOTc4MzQ0Zi04NTk2LTRjM2EtYTk3OC04ZmNhYmEzOTAzYzUiLCJhdWQiOiJkaWQ6ZXhhbXBsZTo0YTU3NTQ2OTczNDM2ZjZmNmM0YTRhNTc1NzMiLCJuYmYiOjE1NDE0OTM3MjQsImlhdCI6MTU0MTQ5MzcyNCwiZXhwIjoxNTczMDI5NzIzLCJub25jZSI6IjM0M3MkRlNGRGEtIiwidnAiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy9leGFtcGxlcy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW5YXRpb24iLCJDcmVkZW50aWFsTWFuYWdlclByZXNlbnRhdGlvbiJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpoYkdjaU9pSlNVekkxTmlJc0luUjVjQ0k2SWtwWFZDSXNJbXRwWkNJNkltUnBaRHBsZUdGdGNHeGxPbUZpWm1VeE0yWTNNVEl4TWpBME16RmpNamMyWlRFeVpXTmhZaU5yWlhsekxURWlmUS5leUp6ZFdJaU9pSmthV1E2WlhoaGJYQnNaVHBsWW1abFlqRm1OekV5WldKak5tWXhZekkzTm1VeE1tVmpNakVpTENKcWRHa2lPaUpvZEhSd09pOHZaWGhoYlhCc1pTNWxaSFV2WTNKbFpHVnVkR2xoYkhNdk16Y3pNaUlzSW1semN5STZJbWgwZEhCek9pOHZaWGhoYlhCc1pTNWpiMjB2YTJWNWN5OW1iMjh1YW5kcklpd2libUptSWpveE5UUXhORGt6TnpJMExDSnBZWFFpT2pFMU5ERTBPVE0zTWpRc0ltVjRjQ0k2TVRVM016QXlPVGN5TXl3aWJtOXVZMlVpT2lJMk5qQWhOak0wTlVaVFpYSWlMQ0oyWXlJNmV5SkFZMjl1ZEdWNGRDSTZXeUpvZEhSd2N6b3ZMM2QzZHk1M015NXZjbWN2TWpBeE9DOWpjbVZrWlc1MGFXRnNjeTkyTVNJc0ltaDBkSEJ6T2k4dmQzZDNMbmN6TG05eVp5OHlNREU0TDJOeVpXUmxiblJwWVd4ekwyVjRZVzF3YkdWekwzWXhJbDBzSW5SNWNHVWlPbHNpVm1WeWFXWnBZV0pzWlVOeVpXUmxiblJwWVd3aUxDSlZibWwyWlhKemFYUjVSR1ZuY21WbFEzSmxaR1Z1ZEdsaGJDSmRMQ0pqY21Wa1pXNTBhV0ZzVTNWaWFtVmpkQ0k2ZXlKa1pXZHlaV1VpT25zaWRIbHdaU0k2SWtKaFkyaGxiRzl5UkdWbmNtVmxJaXdpYm1GdFpTSTZJanh6Y0dGdUlHeGhibWM5SjJaeUxVTkJKejVDWVdOallXeGhkWExEcVdGMElHVnVJRzExYzJseGRXVnpJRzUxYmNPcGNtbHhkV1Z6UEM5emNHRnVQaUo5ZlgxOS5LTEpvNUdBeUJORDNMRFRuOUg3RlFva0VzVUVpOGpLd1hoR3ZvTjNKdFJhNTF4ck5EZ1hEYjBjcTFVVFlCLXJLNEZ0OVlWbVIxTklfWk9GOG9HY183d0FwOFBIYkYySGFXb2RRSW9PQnh4VC00V05xQXhmdDdFVDZsa0gtNFM2VXgzclNHQW1jek1vaEVFZjhlQ2VOLWpDOFdla2RQbDZ6S1pRajBZUEIxcng2WDAteGxGQnM3Y2w2V3Q4cmZCUF90WjlZZ1ZXclFtVVd5cFNpb2MwTVV5aXBobXlFYkxaYWdUeVBsVXlmbEdsRWRxclpBdjZlU2U2UnR4Snk2TTEtbEQ3YTVIVHphbllUV0JQQVVIRFpHeUdLWGRKdy1XX3gwSVdDaEJ6STh0M2twRzI1M2ZnNlYzdFBnSGVLWEU5NGZ6X1FwWWZnLS03a0xzeUJBZlFHYmciXX19.ft_Eq4IniBrr7gtzRfrYj8Vy1aPXuFZU-6_ai0wvaKcsrzI4JkQEKTvbJwdvIeuGuTqy7ipO-EYi7V4TvonPuTRdpB7ZHOlYlbZ4wA9WJ6mSVSqDACvYRiFvrOFmie8rgm6GacWatgO4m4NqiFKFko3r58LueFfGw47NK9RcfOkVQeHCq4btaDqksDKeoTrNysF4YS89INa-prWomrLRAhnwLOo1Etp3E4ESAxg73CR2kA5AoMbf5KtFueWnMcSbQkMRdWcGC1VssC0tB0JffVjq7ZV6OTyV4kl1-UVgiPLXUTpupFfLRhf9QpqMBjYgP62KvhIvW8BbkGUelYMetA%22%5D

jogu avatar Oct 04 '23 12:10 jogu

Thanks for the examples and the url. This really helps.

If your example is correct, then it just means to put a JSON stringified version of the objects in there. I think that sounds reasonable.

A clarification in the spec would be good, and adding a few examples values of what an encoded url looks like, as that will clear up a lot of things. It has caused quite some interop issues and confusion between different implementations for us

TimoGlastra avatar Oct 04 '23 14:10 TimoGlastra

Yes that is also how we implemented it in our SIOP library. The spec does mention in multiple areas that values should be JSON objects, for instance for client/registration metadata. It doesn't make much sense to me to try to url-encode every single key-value from a JSON object, being used as a value. So we simply stringify the JSON object, then use that as a value and then url encode the keys and values

nklomp avatar Oct 05 '23 00:10 nklomp

It doesn't make much sense to me to try to url-encode every single key-value from a JSON object, being used as a value.

Agree, and I think that's part of the confusion of why the response encoding is x-www-form-urlencoded in the first place, when the values are just JSON objects anyways.

So this

I think it's because we're building on top of OAuth and the request to / response from the authorization server are [in base OAuth] passed in the query string.

is some very helpful context to understand the reason.

raleng avatar Oct 05 '23 00:10 raleng

do we need any action on this in the spec? or can this issue be closed as clarified..?

Sakurann avatar Aug 19 '24 22:08 Sakurann

I suspect that this can be closed since we don't have PE any longer...

danielfett avatar Apr 08 '25 11:04 danielfett