France Identitรฉ Partenaires โ OID4VCI Draft 13¶
โ Back to the Partenaires app overview
OID4VCI Draft 13 profile¶
Compatibility profile
This section documents the OID4VCI Draft 13 profile currently exercised with the France Identitรฉ Partenaires test application. Draft 13 is an older implementer's draft, not the current OID4VCI 1.0 final specification. Keep the Draft 13 and final 1.0 protocol surfaces separate.
The profile is intended for a partner that operates its own credential issuer and wants to test issuance into France Identitรฉ Partenaires. The issuer remains responsible for the HTTPS endpoints, OAuth token service, holder-binding proof validation, credential signing, and the trust material required by its test environment. France Identitรฉ acts as the wallet that receives and stores the credential.
The current Partenaires Draft 13 test path has one supported issuance target: the IDAK bicycle attestation. The offer, issuer metadata, Credential Request, and returned credential must all describe the fr.idak.mbicycle.1 credential as an ISO/IEC 18013-5 mdoc. Other credential configurations and SD-JWT VC are outside the supported test scope documented here.
Profile at a glance¶
| Property | Partenaires Draft 13 profile |
|---|---|
| Issuance flow | Pre-authorized code flow |
| Issuer identifier | A dedicated profile URL ending in /protocol/oid4vci/draft-13 |
| Credential Configuration ID | fr.idak.mbicycle.1 |
| Credential format | mso_mdoc only |
| Doctype | fr.idak.mbicycle.1 |
| mdoc binding | cose_key |
| Tested proof | JWT proof with ES256 and a P-256 holder key |
| Token request | application/x-www-form-urlencoded |
| Access-token scheme | Ordinary Bearer; return token_type as lowercase bearer for Partenaires compatibility |
| Proof nonce | c_nonce returned by the current token response |
| Credential request | Singular proof object |
| Credential response | Singular credential member containing the mdoc |
| Separate nonce endpoint | Not part of this profile |
| DPoP / client attestation | Not part of this profile |
The profile intentionally uses a small metadata projection. Optional metadata must not contain malformed values: in particular, an advertised endpoint must be an absolute HTTPS URI, and a logo or other display URI must be a valid URI if it is published.
Known-good reference¶
The live playground mdoc issuer exposes the profile here:
Inspect the live Draft 13 issuer metadata
The current reference configuration is:
| Field | Value |
|---|---|
| Credential Configuration ID | fr.idak.mbicycle.1 |
| Format | mso_mdoc |
| Doctype | fr.idak.mbicycle.1 |
| Binding method | cose_key |
| Issuer signing algorithm | ES256 |
| Proof type | jwt |
| Proof signing algorithm | ES256 |
The issuer identifier and endpoint URLs belong to the partner's service. The credential values in this table are the Partenaires test contract: do not replace fr.idak.mbicycle.1, mso_mdoc, or the doctype with a PID, PhotoID, mDL, SD-JWT VC, or another custom configuration.
Keep Draft 13 separate from OID4VCI 1.0¶
If an issuer also supports OID4VCI 1.0, expose two distinct protocol surfaces. Do not select the protocol by changing a query parameter while keeping the same issuer identifier, and do not allow a wallet to cache one profile's metadata for the other profile.
| Draft 13 | Do not substitute the final 1.0 form |
|---|---|
Issuer identifier includes /protocol/oid4vci/draft-13 |
The canonical final issuer URL |
Singular proof |
proofs |
| Proof nonce from the token response | A separate final-profile nonce exchange |
Authorization: Bearer <access_token> |
Authorization: DPoP <access_token> |
Singular credential response |
Do not require a final-profile credentials array |
The OID4VCI 1.0 external issuance profile describes a different integration. Do not copy its DPoP, client-attestation, JWE, or nonce-endpoint steps into a Draft 13 transaction.
1. Choose the issuer identifier and endpoints¶
Choose the partner's canonical registered issuer URL. The Draft 13 profile issuer is that URL with /protocol/oid4vci/draft-13 appended. For example, if the partner base issuer is https://issuer.example.test/oid4vci, the profile issuer and metadata URL are:
https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13
https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13/.well-known/openid-credential-issuer
The profile issuer is case-sensitive, uses https, and has no query or fragment. Remove a trailing slash before appending the profile suffix.
The credential_issuer value in the metadata, the Credential Offer, and the proof JWT aud claim must be the same exact profile issuer URL. Do not use the token endpoint, credential endpoint, metadata URL, or the unsuffixed issuer URL as the proof audience.
After discovery, use the endpoint values returned in metadata. Do not derive them from a hostname or replace them with relative paths.
2. Publish issuer metadata¶
Serve the metadata endpoint with:
The successful response must be JSON with status 200 and Content-Type: application/json. The endpoint and all advertised URLs must be reachable over TLS.
Minimal mdoc metadata¶
The top-level token_endpoint, grant_types_supported, and singular token_endpoint_auth_method fields below reflect the metadata shape consumed by the Partenaires compatibility path. They are deliberately kept alongside the normal OID4VCI metadata. If the issuer publishes a separate OAuth Authorization Server metadata document, it must remain standards-compliant and use the appropriate RFC 8414 field names, including plural token_endpoint_auth_methods_supported.
{
"credential_issuer": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13",
"credential_endpoint": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13/credential",
"token_endpoint": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13/token",
"notification_endpoint": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13/notification",
"grant_types_supported": [
"urn:ietf:params:oauth:grant-type:pre-authorized_code"
],
"token_endpoint_auth_method": [
"none"
],
"credential_configurations_supported": {
"fr.idak.mbicycle.1": {
"format": "mso_mdoc",
"doctype": "fr.idak.mbicycle.1",
"cryptographic_binding_methods_supported": [
"cose_key"
],
"credential_signing_alg_values_supported": [
"ES256"
],
"proof_types_supported": {
"jwt": {
"proof_signing_alg_values_supported": [
"ES256"
]
}
}
}
}
}
For this Partenaires test path, the configuration key and doctype are both fr.idak.mbicycle.1. They remain separate protocol fields: publish and validate each field explicitly.
Metadata rules¶
credential_issueris the profile issuer identifier, not the base issuer identifier.credential_endpoint,token_endpoint, andnotification_endpointmust be complete absolute HTTPS URIs. Do not publish/credential, an empty string,null, or a URI with a typo.notification_endpointis optional. Omit it if the issuer does not implement notifications. If it is present, implement the endpoint before testing.grant_types_supportedmust includeurn:ietf:params:oauth:grant-type:pre-authorized_code.token_endpoint_auth_method: ["none"]means that this test flow does not require client authentication at the token endpoint.credential_configurations_supportedis a map keyed by the Credential Configuration ID. For this test path it must containfr.idak.mbicycle.1withformat: "mso_mdoc"anddoctype: "fr.idak.mbicycle.1".- For the mdoc configuration, publish the supported binding method, issuer signing algorithms, and proof types and algorithms shown above. Additional credential configurations are outside the current Partenaires issuance test scope.
- Publish only algorithms that the issuer and the Partenaires test path actually support. The known-good profile advertises JWT/
ES256only. - This compatibility profile does not advertise a
nonce_endpoint. Do not add a Final 1.0 nonce endpoint and expect the Partenaires Draft 13 flow to call it. - The live profile does not use
authorization_serversindirection. Keep the Draft 13 metadata self-contained unless a separately tested deployment requires an authorization server. - Use
Cache-Control: no-storeon metadata, offers, token responses, credential responses, and error responses that contain transaction information.
Credential configuration and data declarations¶
The app uses the offer to select the IDAK configuration and then resolves that configuration from issuer metadata. The issuer must therefore keep these values consistent:
credential_configuration_id in the offer
-> fr.idak.mbicycle.1
-> key in credential_configurations_supported
doctype in an mdoc Credential Request
-> fr.idak.mbicycle.1
-> the doctype in that configuration
The protocol steps are metadata-driven, but the current test credential schema is fixed. The mdoc namespace and element identifiers must belong to the declared fr.idak.mbicycle.1 schema and must be encoded with their actual CBOR types. A syntactically valid mdoc with the wrong doctype or unsupported data dictionary may still be rejected or may not be displayed by the wallet.
3. Create the Credential Offer¶
The recommended test invocation is an HTTPS credential_offer_uri:
The offer URL must return application/json and Cache-Control: no-store.
For the supported IDAK mdoc configuration, the response has this shape:
{
"credential_issuer": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13",
"credential_configuration_ids": [
"fr.idak.mbicycle.1"
],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "<single-use-code>",
"user_pin_required": false
}
}
}
The pre-authorized code must be unpredictable, short-lived, single-use, and bound to the issuer and credential configuration. Never put a real code in documentation or logs.
If the offer contains a tx_code object, the wallet must provide the transaction code in the token request. Do not send a tx_code when the offer does not require one.
Keep the credential_issuer in the offer exactly equal to the value in issuer metadata. Do not put the final issuer URL in a Draft 13 offer.
4. Exchange the pre-authorized code for a token¶
The wallet posts the code to the metadata-advertised token_endpoint as an OAuth form request:
POST {token_endpoint} HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Accept: application/json
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code&pre-authorized_code=<URL-encoded-code>
A curl equivalent is:
curl --fail-with-body -X POST "$TOKEN_ENDPOINT" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code' \
--data-urlencode 'pre-authorized_code=<single-use-code>'
If required by the offer, add another --data-urlencode 'tx_code=<value>' field.
For the compatibility profile:
- Do not send the token request as JSON.
- Do not use
Authorization: DPoPor add a DPoP proof. - Do not add client authentication when the metadata advertises
token_endpoint_auth_method: ["none"]. - Return ordinary Bearer access-token semantics. For strict Partenaires compatibility, use lowercase
"bearer"for the JSONtoken_typevalue; do not returnDPoP.
The successful response should include a nonce for the subsequent proof:
{
"access_token": "<opaque-access-token>",
"token_type": "bearer",
"expires_in": 300,
"c_nonce": "<fresh-transaction-nonce>",
"c_nonce_expires_in": 300
}
The c_nonce must be generated for this issuance transaction and associated with the returned access token. The wallet must use that exact value in its next proof. Do not generate a nonce locally, reuse one from another issuer, or call a separate nonce endpoint.
5. Generate the Draft 13 proof JWT¶
Select the proof type and algorithm from the selected configuration, not from another issuer or another credential. For the currently tested Partenaires profile, the intersection is:
The holder creates a fresh compact JWS for every Credential Request. Its protected header is:
{
"typ": "openid4vci-proof+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "<BASE64URL-X>",
"y": "<BASE64URL-Y>"
}
}
Its payload is:
{
"aud": "https://issuer.example.test/oid4vci/protocol/oid4vci/draft-13",
"iat": 1733659200,
"nonce": "<c_nonce-from-this-token-response>"
}
The following checks are mandatory before sending the request:
typis exactlyopenid4vci-proof+jwt.algis one of the algorithms advertised for the selected configuration; for the known-good profile it isES256.- The JWK is public only: do not include the private
dmember. ktyisECandcrvisP-256whenES256is selected.audis exactly the metadatacredential_issuervalue.nonceis thec_noncefrom the current token response.iatis close to the current Unix time.- The private key used to sign the JWS corresponds to the public
xandyvalues in the header JWK. - Local signature verification with the header JWK succeeds before the HTTP request is sent.
The OID4VCI proof JWT is not a DPoP proof. Do not put it in an Authorization: DPoP header and do not use a DPoP nonce in its payload.
Draft 13 also defines a CWT proof type, but the Partenaires compatibility metadata described here advertises the JWT proof. Do not send a CWT unless the target wallet and the selected issuer configuration have been tested for that profile explicitly.
6. Send the Credential Request¶
The request is an HTTP POST to the credential_endpoint from metadata, with the access token as a Bearer token and a singular proof member.
IDAK mdoc request¶
POST {credential_endpoint} HTTP/1.1
Authorization: Bearer <access-token>
Content-Type: application/json
{
"format": "mso_mdoc",
"doctype": "fr.idak.mbicycle.1",
"proof": {
"proof_type": "jwt",
"jwt": "<signed-proof-jwt>"
}
}
The doctype is fixed for this test path: it must be fr.idak.mbicycle.1. If claims is included, it must describe a subset of the IDAK mdoc data that the issuer can issue, and the resulting credential must contain the requested elements.
Do not send this Final 1.0-shaped body:
{
"credential_configuration_id": "fr.idak.mbicycle.1",
"proofs": {
"jwt": [
"<signed-proof-jwt>"
]
}
}
Draft 13 uses proof, not proofs. Although the configuration ID and doctype have the same string value in this test profile, they are separate fields and must both be validated.
Unless metadata explicitly advertises response encryption and the wallet requests it, do not add a credential_response_encryption object or JWE processing to this compatibility flow.
7. Return a wallet-parseable credential¶
Return status 200 with Content-Type: application/json and Cache-Control: no-store.
The Draft 13 response uses the singular credential member:
{
"credential": "<base64url-encoded-credential>",
"notification_id": "<optional-notification-id>",
"c_nonce": "<optional-fresh-nonce>",
"c_nonce_expires_in": 300
}
For binary credentials, the credential value is a base64url string without padding. Do not serialize it as JSON, hex, a Buffer diagnostic object, or a base64 string nested inside another JSON object.
mdoc encoding requirements¶
For the Partenaires-compatible mdoc path, the decoded credential must be a real ISO/IEC 18013-5 CBOR Document container, not an empty diagnostic value and not only a server-side JSON representation of IssuerSigned.
At a structural level, the encoded document contains:
Document
โโโ docType: text string
โโโ issuerSigned
โ โโโ nameSpaces: map(namespace -> array of IssuerSignedItemBytes)
โ โโโ issuerAuth: COSE_Sign1
โโโ deviceSigned
โโโ nameSpaces: DeviceNameSpacesBytes
โโโ deviceAuth: holder-side device authentication structure
The issuer-signed portion must satisfy all of the following:
Document.docTypeequals the requested and advertised doctype.- Each issuer namespace contains
IssuerSignedItemBytes, encoded as CBOR tag 24 around the byte string containing the encodedIssuerSignedItem. - Each item contains the correct
digestID, a cryptographically randomrandomvalue, the namespace element identifier, and the correctly typedelementValue. - The MSO
valueDigestsmap contains the SHA-256 digest of the exact encodedIssuerSignedItemBytes, including the tag and byte-string wrapper. - The MSO
docType,deviceKeyInfo.deviceKey, digest algorithm, and validity information are consistent with the document. issuerAuthis a valid COSESign1signed by the issuer key, and its certificate chain is the issuer chain trusted by the Partenaires test environment.- The public holder key in
deviceKeyInfo.deviceKeycorresponds to the key represented in the proof JWT. Do not generate one holder key, put another key in the proof, and bind a third key into the MSO. - Dates, booleans, numbers, arrays, byte strings, and images use the CBOR types required by the credential schema. Do not encode every value as a string.
- Validity timestamps follow the ISO mdoc date/time rules; do not add fractional seconds where the selected profile forbids them.
The issuer does not create a fake presentation. The wallet supplies the holder/device authentication needed for a later presentation. However, the value returned at issuance must be the complete CBOR credential container accepted by the target wallet; an empty CBOR map, null, JSON {}, or a debugging object containing Buffer values is not a credential.
8. Implement the optional Notification Endpoint¶
If notification_endpoint is advertised and the response contains notification_id, the wallet may send:
POST {notification_endpoint} HTTP/1.1
Authorization: Bearer <access-token>
Content-Type: application/json
{
"notification_id": "<notification-id>",
"event": "credential_accepted"
}
Supported event values are:
event_description is optional and must contain printable US-ASCII characters. The endpoint should be idempotent for a repeated identical notification and return a 2xx response; 204 No Content is appropriate.
The notification endpoint is protected by the same access-token transaction. It is not a replacement for the Credential Endpoint and it must not be used to deliver the credential.
9. Test with a raw HTTP trace before using the app¶
Use a redacted trace and check the following sequence:
GETthe profile-specific metadata URL and verify that it returns JSON and absolute endpoints.- Open the HTTPS
credential_offer_uriin the Partenaires app. - Confirm that the offer's
credential_configuration_idscontains a key present in metadata. - POST the pre-authorized code as a form request to the advertised token endpoint.
- Confirm that the token response contains a fresh
c_nonce. - Generate a new proof JWT with
aud == credential_issuerandnonce == c_nonce. - POST the singular Draft 13 Credential Request with
Authorization: Bearer. - Decode the returned
credentiallocally as base64url then CBOR; verify the mdoc structure, doctype, issuer signature, value digests, and holder-key binding. - Check the credential in the Partenaires app and, if a notification was returned, record the notification response.
Never log pre-authorized codes, access tokens, private keys, full proof JWTs, or raw credentials. Log only redacted protocol facts such as HTTP status, content type, configuration ID, format, doctype, proof type, algorithm, nonce presence, and error codes.
Troubleshooting¶
| Symptom | Check first |
|---|---|
token type ... is not supported |
Return "token_type": "bearer" and use Authorization: Bearer; do not switch to DPoP. |
the field ... endpoint ... is not an URI |
Advertise a complete absolute https:// URI. Do not use /notification, an empty value, or a JSON object. |
| Wallet looks for a nonce endpoint | Remove the Final 1.0 nonce exchange. Return c_nonce in the current token response and use it in the proof. |
proofs rejected or proof missing |
Send one Draft 13 proof object with proof_type: "jwt". |
invalid_proof |
Compare the proof aud, nonce, iat, typ, alg, JWK curve, and signature against the current transaction. |
| mdoc cannot be parsed or appears empty | Decode the response as base64url CBOR, not JSON. Confirm a non-empty issuerSigned.nameSpaces, a valid issuerAuth, a matching MSO digest map, and the full document container. |
| Credential is issued but not shown | Confirm the configuration ID and doctype are both fr.idak.mbicycle.1, and that the namespace, element identifiers, and CBOR data types match the supported IDAK mdoc schema. |
| Credential request reaches the wrong service | Use the exact token_endpoint and credential_endpoint returned by metadata; do not derive URLs from the base issuer. |
| A retry behaves inconsistently | Pre-authorized codes and access tokens are transaction state. Create a new offer and use a new proof for a new attempt. |
When reporting an interoperability issue, retain the original HTTP status and JSON body, including error and error_description. Do not reduce an issuer response to a generic โissuance failedโ message.
Partner acceptance checklist¶
Before asking for a Partenaires test, verify:
- The Draft 13 issuer identifier is distinct from any final 1.0 issuer identifier.
-
/.well-known/openid-credential-issueris appended to the Draft 13 issuer identifier. - Metadata is returned as
application/jsonover HTTPS withCache-Control: no-store. - All advertised endpoint values are absolute HTTPS URIs.
- The offer contains only the supported test configuration
fr.idak.mbicycle.1. - The metadata entry for
fr.idak.mbicycle.1declaresformat: "mso_mdoc"anddoctype: "fr.idak.mbicycle.1". - The token request accepts
application/x-www-form-urlencodedand the pre-authorized-code grant. - The token response returns ordinary Bearer semantics and a transaction-specific
c_nonce. - The proof is a fresh, locally verifiable JWT with
typ: openid4vci-proof+jwt. - The proof uses an algorithm and key curve advertised by the selected configuration.
- The proof
audis the exact Draft 13credential_issuer. - The proof
nonceis thec_noncefrom the same token response. - The Credential Request uses
format: "mso_mdoc",doctype: "fr.idak.mbicycle.1", and singularproof. - The mdoc response is a non-empty, correctly encoded ISO CBOR credential with matching issuer signatures, digests, doctype, and holder key.
- The response uses singular
credential, not a final-profile response shape. - Any advertised notification endpoint accepts the returned access token and is idempotent.
- The test trace contains no private keys, access tokens, pre-authorized codes, or full credentials.
Draft 13 references¶
- OpenID for Verifiable Credential Issuance โ Draft 13, especially the pre-authorized code flow, Credential Request proof, issuer metadata, and the
mso_mdocformat profile. - OpenID for Verifiable Credential Issuance 1.0 Final, for comparison only.
- ISO/IEC 18013 working documents, including mdoc and mobile driving licence material.
- RFC 6749 โ The OAuth 2.0 Authorization Framework for token requests and Bearer authorization semantics.
- RFC 7515 โ JSON Web Signature and RFC 7517 โ JSON Web Key for the proof JWT and public holder JWK.