Access Token Policy
Presentation Definition mapping
Wallet functionality uses Presentation Definitions to determine the required credentials for a given presentation request. An OAuth2 authorization request uses scopes to determine the required permissions for a given request. The mapping between scopes and presentation definitions is defined in a policy definition file.
Configuration
The Nuts config supports the mapping between OAuth2 scopes and presentation definitions using a file-based configuration. The file-based configuration is a simple way to define the mapping between scopes and presentation definitions. It can be used for simple use cases where the mapping is static and does not change often.
To use file-based configuration, you need to define the path to a directory that contains policy definition files:
policy:
directory: /path/to/directory
All JSON files in the directory will be loaded and used to define the mapping between scopes and presentation definitions.
Policy Structure
JSON documents used for policies must have the following structure:
{
"example_scope": {
"organization": {
"id": "example",
"format": {
"ldp_vc": {
"proof_type": ["JsonWebSignature2020"]
},
"ldp_vp": {
"proof_type": ["JsonWebSignature2020"]
}
},
"input_descriptors": [
{
"id": "1",
"constraints": {
"fields": [
{
"path": ["$.type"],
"filter": {
"type": "string",
"const": "HumanCredential"
}
},
{
"id": "fullName",
"path": ["$.credentialSubject.fullName"],
"filter": {
"type": "string"
}
}
]
}
}
]
}
}
}
Where example_scope is the scope that the presentation definition is associated with.
The presentation_definition object contains the presentation definition that should be used for the given scope.
The wallet_owner_type field is used to determine the audience type of the presentation definition, valid values are organization, service_provider and user.
The service_provider block describes the credentials that a service provider acting on behalf of a healthcare provider (the OAuth client in the RFC 7523 jwt-bearer flow) must present.
It applies only to outbound RFC 7523 token requests initiated by the node.
A profile may define any combination of organization, service_provider and user blocks; at least one is required.
OAuth2 Token Introspection field mapping
The input descriptor constraint fields that contain an id property (input_descriptor.contraints.field.id) are returned in the OAuth2 Token Introspection response.
The value of the Verifiable Credential that the matched field is included in the response as claims.
E.g., in the example above, a claim named fullName is added to the introspection response, containing the value of the credentialSubject.fullName property in the Verifiable Credential.
The following is an example OAuth2 Token Introspection response containing the fullName claim from the Presentation Definition
(some fields are omitted for brevity):
{
"iss": "did:web:example.com",
"active": true,
"scope": "example_scope",
"fullName": "John Doe"
}
Writer of policies should take into consideration:
- fields that are intended to be used for logging or authorization decisions should have a distinct identifier.
- claims ideally map a registered claim name (e.g. IANA JWT claims)
- overwriting properties already defined in the token introspection endpoint response is forbidden. These are: iss, sub, exp, iat, active, client_id, scope.
Extracting substrings with regular expressions
If you want introspection to return part of a string, you can use the pattern regular expression filter in the field definition with a capture group.
Token introspection will return the value of the capture group in the regular expression, instead of the whole field value.
For instance, if you want to extract the level from the string "Admin level 4" from the following credential:
{
"credentialSubject": {
"role": "Admin level 4"
}
}
You can define the following field in the input descriptor constraint, to have the level returned in the introspection response as admin_level:
{
"id": "admin_level",
"path": ["$.credentialSubject.role"],
"filter": {
"type": "string"
"pattern": "Admin level ([0-9])"
}
}
Only 1 capture group is supported in regular expressions. If multiple capture groups are defined, an error will be returned.
Two-VP flow and cross-VP binding (experimental)
Warning
The two-VP flow is experimental and gated behind auth.experimental.jwtbearerclient = true (default false).
The service_provider PD block, the service_provider_subject_id API field, and the cross-VP binding mechanism described below are subject to change without notice while the underlying OAuth profile stabilises.
When the two-VP flow runs
By default the node uses a single-VP token request (RFC 021 vp_token-bearer). The two-VP RFC 7523 jwt-bearer flow runs only when all of the following hold:
The experimental flag
auth.experimental.jwtbearerclientistrueon the EHR-side node.The EHR caller passes
service_provider_subject_idin the body ofPOST /internal/auth/v2/{subjectID}/request-service-access-token.The remote authorization server advertises
urn:ietf:params:oauth:grant-type:jwt-bearerin its metadata’sgrant_types_supported.The credential profile referenced by the request scope has a
service_providerPD configured.
If conditions (2)-(4) are not all met when service_provider_subject_id is supplied, the request fails with a clear error rather than silently falling back to the single-VP flow.
How the two VPs are built
VP1 (organization) — built from the wallet of the path-param
subjectID(the healthcare provider, HCP), using the credential profile’sorganizationPD. Sent as theassertionform parameter (RFC 7521 §4.1, the authorization grant).VP2 (service_provider) — built from the wallet of
service_provider_subject_id(the OAuth client, the service provider acting on behalf of the HCP), using the credential profile’sservice_providerPD. Sent as theclient_assertionform parameter (RFC 7521 §4.2, authenticating the client).
Each VP is signed with the holder DID’s keys from the respective wallet.
The credential_selection map
credential_selection is a key-value map (string keys → string values) used to disambiguate between credentials when multiple satisfy a single input descriptor. Keys must match a constraint field id; values are the literal string the field should equal for selection.
There are two sources of entries:
EHR-supplied — the
credential_selectionfield on the request body. EHRs typically use this for runtime context like a patient or encounter identifier (e.g.patient_id).Server-captured (two-VP only) — entries populated automatically from VP1’s matched constraint fields, as described above. The capture is additive: any EHR-supplied key always wins, and only string-typed captured values are merged in.
The same map is consulted by both single-VP and two-VP flows; the only difference is that the two-VP flow may add entries between VP1 and VP2.
Required configuration
To enable the two-VP flow on a node:
Set
auth.experimental.jwtbearerclient: truein the node config (off by default).Provision the service-provider Nuts subject and its wallet via the existing wallet APIs. Its wallet must hold credentials matching the
service_providerPD for any profile that should support the flow.Add a
service_providerPD block to each credential profile that should support the flow.Have the EHR pass
service_provider_subject_idon the access-token request body.