diff --git a/auth/api/iam/generated.go b/auth/api/iam/generated.go index f4c6f46a0..be7b18fb1 100644 --- a/auth/api/iam/generated.go +++ b/auth/api/iam/generated.go @@ -261,6 +261,12 @@ type RequestOpenid4VCICredentialIssuanceJSONBody struct { // issuance per call and only consumes the first entry. AuthorizationDetails []AuthorizationDetail `json:"authorization_details"` + // AuthorizationRequestParams 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 (e.g. auth_method for AET smartcards). + AuthorizationRequestParams *map[string]string `json:"authorization_request_params,omitempty"` + // CredentialRequestParams Optional JSON object overlaid on top of the OpenID4VCI Credential Request body sent to // the issuer's credential endpoint. Any field supplied here overrides the node's default — // including credential_configuration_id, credential_identifier and proofs. Use this for diff --git a/auth/api/iam/openid4vci.go b/auth/api/iam/openid4vci.go index 1f9fd19fe..9bc5f82fb 100644 --- a/auth/api/iam/openid4vci.go +++ b/auth/api/iam/openid4vci.go @@ -128,7 +128,7 @@ func (r Wrapper) RequestOpenid4VCICredentialIssuance(ctx context.Context, reques if err != nil { return nil, fmt.Errorf("failed to parse the authorization_endpoint: %w", err) } - redirectUrl := nutsHttp.AddQueryParams(*authorizationEndpoint, map[string]string{ + authzParams := map[string]string{ oauth.ResponseTypeParam: oauth.CodeResponseType, oauth.StateParam: state, oauth.ClientIDParam: clientID.String(), @@ -137,7 +137,15 @@ func (r Wrapper) RequestOpenid4VCICredentialIssuance(ctx context.Context, reques oauth.RedirectURIParam: redirectUri.String(), oauth.CodeChallengeParam: pkceParams.Challenge, oauth.CodeChallengeMethodParam: pkceParams.ChallengeMethod, - }) + } + // 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 + } + } + redirectUrl := nutsHttp.AddQueryParams(*authorizationEndpoint, authzParams) return RequestOpenid4VCICredentialIssuance200JSONResponse{ RedirectURI: redirectUrl.String(), diff --git a/auth/api/iam/openid4vci_test.go b/auth/api/iam/openid4vci_test.go index d70844d7d..690fad257 100644 --- a/auth/api/iam/openid4vci_test.go +++ b/auth/api/iam/openid4vci_test.go @@ -78,6 +78,20 @@ func TestWrapper_RequestOpenid4VCICredentialIssuance(t *testing.T) { assert.Equal(t, "code", redirectUri.Query().Get("response_type")) assert.Equal(t, `[{"credential_configuration_id":"UniversityDegreeCredential","format":"vc+sd-jwt","type":"openid_credential"}]`, redirectUri.Query().Get("authorization_details")) }) + t.Run("ok - authorization_request_params merged into authorization request", func(t *testing.T) { + ctx := newTestClient(t) + ctx.openid4vciClient.EXPECT().OpenIDCredentialIssuerMetadata(nil, issuerClientID).Return(&metadata, nil) + ctx.iamClient.EXPECT().AuthorizationServerMetadata(nil, authServer).Return(&authzMetadata, nil) + req := requestCredentials(holderSubjectID, issuerClientID, redirectURI) + req.Body.AuthorizationRequestParams = &map[string]string{"auth_method": "SmartCard"} + + response, err := ctx.client.RequestOpenid4VCICredentialIssuance(nil, req) + + require.NoError(t, err) + redirectUri, err := url.Parse(response.(RequestOpenid4VCICredentialIssuance200JSONResponse).RedirectURI) + require.NoError(t, err) + assert.Equal(t, "SmartCard", redirectUri.Query().Get("auth_method")) + }) t.Run("ok - credential_request_params persisted into session", func(t *testing.T) { ctx := newTestClient(t) ctx.openid4vciClient.EXPECT().OpenIDCredentialIssuerMetadata(nil, issuerClientID).Return(&metadata, nil) diff --git a/docs/_static/auth/v2.yaml b/docs/_static/auth/v2.yaml index 3779a5c5f..638f22d10 100644 --- a/docs/_static/auth/v2.yaml +++ b/docs/_static/auth/v2.yaml @@ -187,6 +187,19 @@ paths: { "some-requested-credential-attribute": "900184590" } + 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 (e.g. auth_method for AET smartcards). + example: | + { + "auth_method": "SmartCard" + } redirect_uri: type: string description: | diff --git a/e2e-tests/browser/client/iam/generated.go b/e2e-tests/browser/client/iam/generated.go index 840d47715..eae3456c7 100644 --- a/e2e-tests/browser/client/iam/generated.go +++ b/e2e-tests/browser/client/iam/generated.go @@ -255,6 +255,12 @@ type RequestOpenid4VCICredentialIssuanceJSONBody struct { // issuance per call and only consumes the first entry. AuthorizationDetails []AuthorizationDetail `json:"authorization_details"` + // AuthorizationRequestParams 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 (e.g. auth_method for AET smartcards). + AuthorizationRequestParams *map[string]string `json:"authorization_request_params,omitempty"` + // CredentialRequestParams Optional JSON object overlaid on top of the OpenID4VCI Credential Request body sent to // the issuer's credential endpoint. Any field supplied here overrides the node's default — // including credential_configuration_id, credential_identifier and proofs. Use this for