Context
Follow-up to the OpenID4VCI client work (#4316). While integrating a Nuts node with the AET ZORG-ID issuer, we found that AET's authorization server (OpenIddict) needs an extra query parameter on the authorization request to start smartcard login. From AET's docs:
In order to use the ZORG-ID smartcard an extra redirect parameter needs to be added to the redirect in the authorize call: auth_method=SmartCard.
The Nuts requestCredential API could not add this parameter, so the redirect to AET never carried auth_method=SmartCard and the smartcard app (zorgid://) was never launched.
Problem
The requestOpenid4VCICredentialIssuance request body has one field for passing extra parameters, credential_request_params, but it only affects the Credential Request (the call to the credential endpoint, auth/openid4vci/client.go -> RequestCredential). It does not affect the authorization request.
The authorization request query is a fixed list, built in RequestOpenid4VCICredentialIssuance (auth/api/iam/openid4vci.go:131): response_type, state, client_id, client_id_scheme, authorization_details, redirect_uri, code_challenge, code_challenge_method. There is no way for the caller to add anything to it.
| Field |
Affects which request |
Can add to the authorize redirect? |
credential_request_params |
Credential Request (POST to credential endpoint) |
No |
| (nothing today) |
Authorization request |
-- |
So any issuer that needs an extra authorization parameter (AET's auth_method, and probably others) cannot be used through Nuts.
The ideal way to pass such information is the standard authorization_details (RFC 9396), which Nuts already sends. AET does not accept the smartcard selection there; it requires a separate top-level auth_method query parameter. This field is the workaround for issuers that, like AET, do not support the standard mechanism.
Proposed change
Add a new optional field authorization_request_params to the requestCredential body. The node adds these key/value pairs to the authorization request, the same way credential_request_params works for the credential request. They are applied after the node's own parameters, so if the caller passes a key the node also sets, the caller's value is used. The caller is responsible for the result.
OpenAPI (docs/_static/auth/v2.yaml, requestOpenid4VCICredentialIssuance body):
authorization_request_params:
type: object
additionalProperties:
type: string
description: |
Optional key/value pairs added to the OpenID4VCI authorization request (the redirect to the
Authorization Server's authorization_endpoint). If a key is also set by the node, the value given here is used.
Prefer authorization_details (RFC 9396) where the issuer supports it; use this only for issuers that require
non-standard authorization parameters.
example: |
{ "auth_method": "SmartCard" }
Handler (auth/api/iam/openid4vci.go, just before the redirect URL is built):
// Optional caller-supplied authorization request parameters, for issuers that need extras
// (e.g. auth_method=SmartCard). Applied after the node's own parameters, so caller values win.
if request.Body.AuthorizationRequestParams != nil {
for key, value := range *request.Body.AuthorizationRequestParams {
authzParams[key] = value
}
}
Request example:
{
"issuer": "https://issuer.example.com/oauth",
"wallet_did": "did:web:example.com",
"authorization_details": [ { "type": "openid_credential", "credential_configuration_id": "HealthcareProfessionalDelegationCredential" } ],
"redirect_uri": "https://example.com/oauth2/org1/callback",
"authorization_request_params": { "auth_method": "SmartCard" }
}
Resulting redirect: .../oauth/connect/authorize?...&auth_method=SmartCard.
Scope
docs/_static/auth/v2.yaml -- new request-body field; run make gen-api (regenerates auth/api/iam/generated.go, e2e-tests/browser/client/iam/generated.go).
auth/api/iam/openid4vci.go -- add the parameters in RequestOpenid4VCICredentialIssuance.
- Tests (
auth/api/iam/openid4vci_test.go): check the parameter ends up in the redirect query.
Considerations
- Naming follows
credential_request_params. The first implementation on project-gf-pilot used the shorter authorization_params; rename it to authorization_request_params before it reaches master.
- Because caller values are applied last, a caller could overwrite parameters the node sets (
client_id, redirect_uri, etc.). This matches how credential_request_params already works. If we want to prevent this, we can deny a small set of keys (for example code_challenge).
- Values are strings (
map[string]string), since these are URL query parameters.
- Separate from
credential_request_params; the two apply to different requests and do not overlap.
Related
Context
Follow-up to the OpenID4VCI client work (#4316). While integrating a Nuts node with the AET ZORG-ID issuer, we found that AET's authorization server (OpenIddict) needs an extra query parameter on the authorization request to start smartcard login. From AET's docs:
The Nuts
requestCredentialAPI could not add this parameter, so the redirect to AET never carriedauth_method=SmartCardand the smartcard app (zorgid://) was never launched.Problem
The
requestOpenid4VCICredentialIssuancerequest body has one field for passing extra parameters,credential_request_params, but it only affects the Credential Request (the call to the credential endpoint,auth/openid4vci/client.go->RequestCredential). It does not affect the authorization request.The authorization request query is a fixed list, built in
RequestOpenid4VCICredentialIssuance(auth/api/iam/openid4vci.go:131):response_type, state, client_id, client_id_scheme, authorization_details, redirect_uri, code_challenge, code_challenge_method. There is no way for the caller to add anything to it.credential_request_paramsSo any issuer that needs an extra authorization parameter (AET's
auth_method, and probably others) cannot be used through Nuts.The ideal way to pass such information is the standard
authorization_details(RFC 9396), which Nuts already sends. AET does not accept the smartcard selection there; it requires a separate top-levelauth_methodquery parameter. This field is the workaround for issuers that, like AET, do not support the standard mechanism.Proposed change
Add a new optional field
authorization_request_paramsto therequestCredentialbody. The node adds these key/value pairs to the authorization request, the same waycredential_request_paramsworks for the credential request. They are applied after the node's own parameters, so if the caller passes a key the node also sets, the caller's value is used. The caller is responsible for the result.OpenAPI (
docs/_static/auth/v2.yaml,requestOpenid4VCICredentialIssuancebody):Handler (
auth/api/iam/openid4vci.go, just before the redirect URL is built):Request example:
{ "issuer": "https://issuer.example.com/oauth", "wallet_did": "did:web:example.com", "authorization_details": [ { "type": "openid_credential", "credential_configuration_id": "HealthcareProfessionalDelegationCredential" } ], "redirect_uri": "https://example.com/oauth2/org1/callback", "authorization_request_params": { "auth_method": "SmartCard" } }Resulting redirect:
.../oauth/connect/authorize?...&auth_method=SmartCard.Scope
docs/_static/auth/v2.yaml-- new request-body field; runmake gen-api(regeneratesauth/api/iam/generated.go,e2e-tests/browser/client/iam/generated.go).auth/api/iam/openid4vci.go-- add the parameters inRequestOpenid4VCICredentialIssuance.auth/api/iam/openid4vci_test.go): check the parameter ends up in the redirect query.Considerations
credential_request_params. The first implementation onproject-gf-pilotused the shorterauthorization_params; rename it toauthorization_request_paramsbefore it reaches master.client_id,redirect_uri, etc.). This matches howcredential_request_paramsalready works. If we want to prevent this, we can deny a small set of keys (for examplecode_challenge).map[string]string), since these are URL query parameters.credential_request_params; the two apply to different requests and do not overlap.Related