Missing examples of presentation submission encoding
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?
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.
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.
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
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
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
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
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.
do we need any action on this in the spec? or can this issue be closed as clarified..?
I suspect that this can be closed since we don't have PE any longer...