From 50c1a55dcb5d00253c64d50a0e03107fdc7a7175 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Mon, 24 Jun 2024 08:43:44 +0200 Subject: [PATCH] rename-again --- auth/api/iam/api.go | 10 ++--- auth/api/iam/generated.go | 5 ++- auth/api/iam/s2s_vptoken.go | 5 ++- auth/api/iam/s2s_vptoken_test.go | 46 +++++++++++------------ auth/api/iam/validation.go | 6 ++- auth/client/iam/interface.go | 2 +- auth/client/iam/mock.go | 8 ++-- auth/client/iam/openid4vp.go | 11 ++---- docs/_static/auth/v2.yaml | 4 +- e2e-tests/browser/client/iam/generated.go | 5 ++- e2e-tests/oauth-flow/rfc021/do-test.sh | 2 +- 11 files changed, 54 insertions(+), 50 deletions(-) diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index 0e90b35d0b..8ecbd651d7 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -232,7 +232,8 @@ func (r Wrapper) HandleTokenRequest(ctx context.Context, request HandleTokenRequ Description: "missing required parameters", } } - return r.handleS2SAccessTokenRequest(ctx, *ownDID, *request.Body.Scope, *request.Body.PresentationSubmission, *request.Body.Assertion) + httpRequest := ctx.Value(httpRequestContextKey{}).(*http.Request) + return r.handleS2SAccessTokenRequest(ctx, *ownDID, httpRequest.URL, *request.Body.Scope, *request.Body.PresentationSubmission, *request.Body.Assertion) default: return nil, oauth.OAuth2Error{ Code: oauth.UnsupportedGrantType, @@ -719,16 +720,15 @@ func (r Wrapper) RequestServiceAccessToken(ctx context.Context, request RequestS } // resolve verifier metadata - requestVerifier, err := did.ParseDID(request.Body.Verifier) - if err != nil { - return nil, core.InvalidInputError("invalid verifier: %w", err) + if request.Body.Verifier == "" { + return nil, core.InvalidInputError("invalid verifier") } useDPoP := true if request.Body.TokenType != nil && strings.EqualFold(string(*request.Body.TokenType), AccessTokenTypeBearer) { useDPoP = false } - tokenResult, err := r.auth.IAMClient().RequestRFC021AccessToken(ctx, *requestHolder, *requestVerifier, request.Body.Scope, useDPoP) + tokenResult, err := r.auth.IAMClient().RequestRFC021AccessToken(ctx, *requestHolder, request.Body.Verifier, request.Body.Scope, useDPoP) if err != nil { // this can be an internal server error, a 400 oauth error or a 412 precondition failed if the wallet does not contain the required credentials return nil, err diff --git a/auth/api/iam/generated.go b/auth/api/iam/generated.go index d57a90ca3f..0f78068b7c 100644 --- a/auth/api/iam/generated.go +++ b/auth/api/iam/generated.go @@ -129,12 +129,15 @@ type RequestObjectResponse = string // ServiceAccessTokenRequest Request for an access token for a service. type ServiceAccessTokenRequest struct { + // AuthorizationServer OAuth2 Authorization Server issuer as specified by RFC 8414. It is the base URL which will be used to + // discover the OAuth2 Authorization Server metadata. + AuthorizationServer string `json:"authorization_server"` + // Scope The scope that will be the service for which this access token can be used. Scope string `json:"scope"` // TokenType The type of access token that is preferred, default: DPoP TokenType *ServiceAccessTokenRequestTokenType `json:"token_type,omitempty"` - Verifier string `json:"verifier"` } // ServiceAccessTokenRequestTokenType The type of access token that is preferred, default: DPoP diff --git a/auth/api/iam/s2s_vptoken.go b/auth/api/iam/s2s_vptoken.go index d0fff2f01e..edd3d94e56 100644 --- a/auth/api/iam/s2s_vptoken.go +++ b/auth/api/iam/s2s_vptoken.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "time" "github.com/nuts-foundation/go-did/did" @@ -43,7 +44,7 @@ const s2sMaxClockSkew = 5 * time.Second // handleS2SAccessTokenRequest handles the /token request with vp_token bearer grant type, intended for service-to-service exchanges. // It performs cheap checks first (parameter presence and validity, matching VCs to the presentation definition), then the more expensive ones (checking signatures). -func (r Wrapper) handleS2SAccessTokenRequest(ctx context.Context, issuer did.DID, scope string, submissionJSON string, assertionJSON string) (HandleTokenRequestResponseObject, error) { +func (r Wrapper) handleS2SAccessTokenRequest(ctx context.Context, issuer did.DID, requestURL *url.URL, scope string, submissionJSON string, assertionJSON string) (HandleTokenRequestResponseObject, error) { pexEnvelope, err := pe.ParseEnvelope([]byte(assertionJSON)) if err != nil { return nil, oauth.OAuth2Error{ @@ -70,7 +71,7 @@ func (r Wrapper) handleS2SAccessTokenRequest(ctx context.Context, issuer did.DID } else { credentialSubjectID = *subjectDID } - if err := r.validatePresentationAudience(presentation, issuer); err != nil { + if err := r.validatePresentationAudience(presentation, requestURL); err != nil { return nil, err } } diff --git a/auth/api/iam/s2s_vptoken_test.go b/auth/api/iam/s2s_vptoken_test.go index 847034ad16..36cf189ace 100644 --- a/auth/api/iam/s2s_vptoken_test.go +++ b/auth/api/iam/s2s_vptoken_test.go @@ -121,7 +121,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx.vcVerifier.EXPECT().VerifyVP(presentation, true, true, gomock.Any()).Return(presentation.VerifiableCredential, nil) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil) - resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.NoError(t, err) require.IsType(t, HandleTokenRequest200JSONResponse{}, resp) @@ -137,7 +137,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { require.NoError(t, token.Remove(jwt.ExpirationKey)) }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation is missing creation or expiration date") }) @@ -147,7 +147,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { require.NoError(t, token.Remove(jwt.NotBeforeKey)) }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation is missing creation or expiration date") }) @@ -157,7 +157,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { require.NoError(t, token.Set(jwt.ExpirationKey, time.Now().Add(time.Hour))) }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation is valid for too long (max 5s)") }) @@ -169,7 +169,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil) ctx.vcVerifier.EXPECT().VerifyVP(presentation, true, true, gomock.Any()).Return(presentation.VerifiableCredential, nil) - resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.NoError(t, err) require.IsType(t, HandleTokenRequest200JSONResponse{}, resp) @@ -181,7 +181,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { }) t.Run("VP is not valid JSON", func(t *testing.T) { ctx := newTestClient(t) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, "[true, false]") + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, "[true, false]") assert.EqualError(t, err, "invalid_request - assertion parameter is invalid: unable to parse PEX envelope as verifiable presentation: invalid JWT") assert.Nil(t, resp) @@ -193,7 +193,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { secondPresentation := test.CreateJSONLDPresentation(t, secondSubjectID, proofVisitor, test.JWTNutsOrganizationCredential(t, secondSubjectID)) assertionJSON, _ := json.Marshal([]VerifiablePresentation{presentation, secondPresentation}) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, string(assertionJSON)) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, string(assertionJSON)) assert.EqualError(t, err, "invalid_request - not all presentations have the same credential subject ID") assert.Nil(t, resp) }) @@ -203,10 +203,10 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx.vcVerifier.EXPECT().VerifyVP(presentation, true, true, gomock.Any()).Return(presentation.VerifiableCredential, nil) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil).Times(2) - _, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.NoError(t, err) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - presentation nonce has already been used") assert.Nil(t, resp) }) @@ -219,7 +219,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { }) presentation := test.CreateJSONLDPresentation(t, *subjectDID, proofVisitor, verifiableCredential) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - presentation has invalid/missing nonce") assert.Nil(t, resp) }) @@ -232,7 +232,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { }) presentation := test.CreateJSONLDPresentation(t, *subjectDID, proofVisitor, verifiableCredential) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - presentation has invalid/missing nonce") assert.Nil(t, resp) }) @@ -244,7 +244,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _ = token.Remove("nonce") }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation has invalid/missing nonce") }) @@ -256,7 +256,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _ = token.Set("nonce", "") }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation has invalid/missing nonce") }) @@ -268,7 +268,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { _ = token.Set("nonce", true) }, verifiableCredential) - _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + _, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) require.EqualError(t, err, "invalid_request - presentation has invalid/missing nonce") }) @@ -278,7 +278,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx := newTestClient(t) presentation, _ := test.CreateJWTPresentation(t, *subjectDID, nil, verifiableCredential) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - expected: did:web:example.com:iam:123, got: [] - presentation audience/domain is missing or does not match") assert.Nil(t, resp) @@ -289,7 +289,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { require.NoError(t, token.Set(jwt.AudienceKey, "did:example:other")) }, verifiableCredential) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - expected: did:web:example.com:iam:123, got: [did:example:other] - presentation audience/domain is missing or does not match") assert.Nil(t, resp) @@ -300,7 +300,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx.vcVerifier.EXPECT().VerifyVP(presentation, true, true, gomock.Any()).Return(nil, errors.New("invalid")) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil) - resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - invalid - presentation(s) or contained credential(s) are invalid") assert.Nil(t, resp) @@ -312,7 +312,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { CredentialSubject: []interface{}{map[string]string{}}, }) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, `invalid_request - unable to get subject DID from VC: credential subjects have no ID`) assert.Nil(t, resp) @@ -327,7 +327,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { } verifiablePresentationJSON, _ := verifiablePresentation.MarshalJSON() - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, string(verifiablePresentationJSON)) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, string(verifiablePresentationJSON)) assert.EqualError(t, err, `invalid_request - presentation signer is not credential subject`) assert.Nil(t, resp) @@ -336,7 +336,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { t.Run("submission is not valid JSON", func(t *testing.T) { ctx := newTestClient(t) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, "not-a-valid-submission", presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, "not-a-valid-submission", presentation.Raw()) assert.EqualError(t, err, `invalid_request - invalid presentation submission: invalid character 'o' in literal null (expecting 'u')`) assert.Nil(t, resp) @@ -345,7 +345,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx := newTestClient(t) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, "everything").Return(nil, policy.ErrNotFound) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, "everything", submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, "everything", submissionJSON, presentation.Raw()) assert.EqualError(t, err, `invalid_scope - not found - unsupported scope (everything) for presentation exchange: not found`) assert.Nil(t, resp) @@ -367,7 +367,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { ctx := newTestClient(t) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil) - resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(context.Background(), issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) assert.EqualError(t, err, "invalid_request - presentation submission does not conform to presentation definition (id=)") assert.Nil(t, resp) }) @@ -378,7 +378,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { contextWithValue := context.WithValue(context.Background(), httpRequestContextKey{}, httpRequest) ctx.policy.EXPECT().PresentationDefinitions(gomock.Any(), issuerDID, requestedScope).Return(walletOwnerMapping, nil) - resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, requestedScope, submissionJSON, presentation.Raw()) + resp, err := ctx.client.handleS2SAccessTokenRequest(contextWithValue, issuerDID, nil, requestedScope, submissionJSON, presentation.Raw()) _ = assertOAuthErrorWithCode(t, err, oauth.InvalidDPopProof, "DPoP header is invalid") assert.Nil(t, resp) diff --git a/auth/api/iam/validation.go b/auth/api/iam/validation.go index 80ec10a225..d1799fb76c 100644 --- a/auth/api/iam/validation.go +++ b/auth/api/iam/validation.go @@ -28,6 +28,7 @@ import ( "github.com/nuts-foundation/nuts-node/policy" "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vcr/pe" + "net/url" ) // validatePresentationSigner checks if the presenter of the VP is the same as the subject of the VCs being presented. @@ -51,7 +52,7 @@ func validatePresentationSigner(presentation vc.VerifiablePresentation, expected // validatePresentationAudience checks if the presentation audience (aud claim for JWTs, domain property for JSON-LD proofs) contains the issuer DID. // it returns an OAuth2 error if the audience is missing or does not match the issuer. -func (r Wrapper) validatePresentationAudience(presentation vc.VerifiablePresentation, issuer did.DID) error { +func (r Wrapper) validatePresentationAudience(presentation vc.VerifiablePresentation, requestURL *url.URL) error { var audience []string switch presentation.Format() { case vc.JWTPresentationProofFormat: @@ -65,8 +66,9 @@ func (r Wrapper) validatePresentationAudience(presentation vc.VerifiablePresenta audience = []string{*proof.Domain} } } + issuer := requestURL.String() for _, aud := range audience { - if aud == issuer.String() { + if aud == issuer { return nil } } diff --git a/auth/client/iam/interface.go b/auth/client/iam/interface.go index 104a32ea55..d8c10efd2b 100644 --- a/auth/client/iam/interface.go +++ b/auth/client/iam/interface.go @@ -45,7 +45,7 @@ type Client interface { // PresentationDefinition returns the presentation definition from the given endpoint. PresentationDefinition(ctx context.Context, endpoint string) (*pe.PresentationDefinition, error) // RequestRFC021AccessToken is called by the local EHR node to request an access token from a remote Nuts node using Nuts RFC021. - RequestRFC021AccessToken(ctx context.Context, requestHolder did.DID, verifier did.DID, scopes string, useDPoP bool) (*oauth.TokenResponse, error) + RequestRFC021AccessToken(ctx context.Context, requestHolder did.DID, oauthIssuer string, scopes string, useDPoP bool) (*oauth.TokenResponse, error) OpenIdCredentialIssuerMetadata(ctx context.Context, oauthIssuerURI string) (*oauth.OpenIDCredentialIssuerMetadata, error) diff --git a/auth/client/iam/mock.go b/auth/client/iam/mock.go index 8a9a1423e3..f7137fcf59 100644 --- a/auth/client/iam/mock.go +++ b/auth/client/iam/mock.go @@ -179,18 +179,18 @@ func (mr *MockClientMockRecorder) RequestObjectByPost(ctx, requestURI, walletMet } // RequestRFC021AccessToken mocks base method. -func (m *MockClient) RequestRFC021AccessToken(ctx context.Context, requestHolder, verifier did.DID, scopes string, useDPoP bool) (*oauth.TokenResponse, error) { +func (m *MockClient) RequestRFC021AccessToken(ctx context.Context, requestHolder did.DID, oauthIssuer, scopes string, useDPoP bool) (*oauth.TokenResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RequestRFC021AccessToken", ctx, requestHolder, verifier, scopes, useDPoP) + ret := m.ctrl.Call(m, "RequestRFC021AccessToken", ctx, requestHolder, oauthIssuer, scopes, useDPoP) ret0, _ := ret[0].(*oauth.TokenResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // RequestRFC021AccessToken indicates an expected call of RequestRFC021AccessToken. -func (mr *MockClientMockRecorder) RequestRFC021AccessToken(ctx, requestHolder, verifier, scopes, useDPoP any) *gomock.Call { +func (mr *MockClientMockRecorder) RequestRFC021AccessToken(ctx, requestHolder, oauthIssuer, scopes, useDPoP any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestRFC021AccessToken", reflect.TypeOf((*MockClient)(nil).RequestRFC021AccessToken), ctx, requestHolder, verifier, scopes, useDPoP) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestRFC021AccessToken", reflect.TypeOf((*MockClient)(nil).RequestRFC021AccessToken), ctx, requestHolder, oauthIssuer, scopes, useDPoP) } // VerifiableCredentials mocks base method. diff --git a/auth/client/iam/openid4vp.go b/auth/client/iam/openid4vp.go index 76414ba892..1f861233d8 100644 --- a/auth/client/iam/openid4vp.go +++ b/auth/client/iam/openid4vp.go @@ -38,7 +38,6 @@ import ( nutsHttp "github.com/nuts-foundation/nuts-node/http" "github.com/nuts-foundation/nuts-node/vcr/holder" "github.com/nuts-foundation/nuts-node/vcr/pe" - "github.com/nuts-foundation/nuts-node/vdr/didweb" "github.com/nuts-foundation/nuts-node/vdr/resolver" ) @@ -205,13 +204,9 @@ func (c *OpenID4VPClient) AccessToken(ctx context.Context, code string, tokenEnd return &token, nil } -func (c *OpenID4VPClient) RequestRFC021AccessToken(ctx context.Context, requester did.DID, verifier did.DID, scopes string, useDPoP bool) (*oauth.TokenResponse, error) { +func (c *OpenID4VPClient) RequestRFC021AccessToken(ctx context.Context, requester did.DID, issuer string, scopes string, useDPoP bool) (*oauth.TokenResponse, error) { iamClient := c.httpClient - oauthIssuer, err := didweb.DIDToURL(verifier) - if err != nil { - return nil, err - } - metadata, err := iamClient.OAuthAuthorizationServerMetadata(ctx, oauthIssuer.String()) + metadata, err := iamClient.OAuthAuthorizationServerMetadata(ctx, issuer) if err != nil { return nil, fmt.Errorf("failed to retrieve remote OAuth Authorization Server metadata: %w", err) } @@ -230,7 +225,7 @@ func (c *OpenID4VPClient) RequestRFC021AccessToken(ctx context.Context, requeste } params := holder.BuildParams{ - Audience: verifier.String(), + Audience: issuer, Expires: time.Now().Add(time.Second * 5), Nonce: nutsCrypto.GenerateNonce(), } diff --git a/docs/_static/auth/v2.yaml b/docs/_static/auth/v2.yaml index 27ab7d420c..268e012d4c 100644 --- a/docs/_static/auth/v2.yaml +++ b/docs/_static/auth/v2.yaml @@ -382,10 +382,10 @@ components: type: object description: Request for an access token for a service. required: - - auth_server_url + - authorization_server - scope properties: - auth_server_url: + authorization_server: type: string description: | OAuth2 Authorization Server issuer as specified by RFC 8414. It is the base URL which will be used to diff --git a/e2e-tests/browser/client/iam/generated.go b/e2e-tests/browser/client/iam/generated.go index 73627865b2..0d8e558807 100644 --- a/e2e-tests/browser/client/iam/generated.go +++ b/e2e-tests/browser/client/iam/generated.go @@ -127,12 +127,15 @@ type RedirectResponseWithID struct { // ServiceAccessTokenRequest Request for an access token for a service. type ServiceAccessTokenRequest struct { + // AuthorizationServer OAuth2 Authorization Server issuer as specified by RFC 8414. It is the base URL which will be used to + // discover the OAuth2 Authorization Server metadata. + AuthorizationServer string `json:"authorization_server"` + // Scope The scope that will be the service for which this access token can be used. Scope string `json:"scope"` // TokenType The type of access token that is preferred, default: DPoP TokenType *ServiceAccessTokenRequestTokenType `json:"token_type,omitempty"` - Verifier string `json:"verifier"` } // ServiceAccessTokenRequestTokenType The type of access token that is preferred, default: DPoP diff --git a/e2e-tests/oauth-flow/rfc021/do-test.sh b/e2e-tests/oauth-flow/rfc021/do-test.sh index e9fbb814c0..a57554fd33 100755 --- a/e2e-tests/oauth-flow/rfc021/do-test.sh +++ b/e2e-tests/oauth-flow/rfc021/do-test.sh @@ -65,7 +65,7 @@ echo "---------------------------------------" echo "Perform OAuth 2.0 rfc021 flow..." echo "---------------------------------------" # Request access token -REQUEST="{\"verifier\":\"${VENDOR_A_DID}\",\"scope\":\"test\"}" +REQUEST="{\"authorization_server\":\"https://nodeA/${VENDOR_A_DID}\",\"scope\":\"test\"}" RESPONSE=$(echo $REQUEST | curl -X POST -s --data-binary @- http://localhost:28081/internal/auth/v2/$VENDOR_B_DID/request-service-access-token -H "Content-Type: application/json" -v) if echo $RESPONSE | grep -q "access_token"; then echo $RESPONSE | sed -E 's/.*"access_token":"([^"]*).*/\1/' > ./node-B/accesstoken.txt