Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions auth/api/auth/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func (m *mockAuthClient) OpenID4VCIClient() openid4vci.Client {
return nil
}

func (m *mockAuthClient) OAuthClientCredentials(_ string) (*pkg2.OAuthClientConfig, bool) {
return nil, false
}

func (m *mockAuthClient) ContractNotary() services.ContractNotary {
return m.contractNotary
}
Expand Down
17 changes: 14 additions & 3 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func TestWrapper_Callback(t *testing.T) {
putState(ctx, "state", withDPoP)
putToken(ctx, token)
codeVerifier := getState(ctx, state).PKCEParams.Verifier
ctx.iamClient.EXPECT().AccessToken(gomock.Any(), code, session.TokenEndpoint, "https://example.com/oauth2/holder/callback", holderSubjectID, holderClientID, codeVerifier, true).Return(&oauth.TokenResponse{AccessToken: "access"}, nil)
ctx.iamClient.EXPECT().AccessToken(gomock.Any(), code, session.TokenEndpoint, "https://example.com/oauth2/holder/callback", holderSubjectID, holderClientID, "", codeVerifier, true).Return(&oauth.TokenResponse{AccessToken: "access"}, nil)

res, err := ctx.client.Callback(nil, CallbackRequestObject{
SubjectID: holderSubjectID,
Expand Down Expand Up @@ -512,7 +512,7 @@ func TestWrapper_Callback(t *testing.T) {
})
putToken(ctx, token)
codeVerifier := getState(ctx, state).PKCEParams.Verifier
ctx.iamClient.EXPECT().AccessToken(gomock.Any(), code, session.TokenEndpoint, "https://example.com/oauth2/holder/callback", holderSubjectID, holderClientID, codeVerifier, false).Return(&oauth.TokenResponse{AccessToken: "access"}, nil)
ctx.iamClient.EXPECT().AccessToken(gomock.Any(), code, session.TokenEndpoint, "https://example.com/oauth2/holder/callback", holderSubjectID, holderClientID, "", codeVerifier, false).Return(&oauth.TokenResponse{AccessToken: "access"}, nil)

res, err := ctx.client.Callback(nil, CallbackRequestObject{
SubjectID: holderSubjectID,
Expand Down Expand Up @@ -1649,6 +1649,8 @@ type testCtx struct {
subjectManager *didsubject.MockManager
jar *MockJAR
openid4vciClient *openid4vci.MockClient
// oauthClientCredentials, when set, is returned by the auth mock's OAuthClientCredentials for a matching ServerURL.
oauthClientCredentials *auth.OAuthClientConfig
}

func newTestClient(t testing.TB) *testCtx {
Expand Down Expand Up @@ -1704,7 +1706,7 @@ func newCustomTestClient(t testing.TB, publicURL *url.URL, authEndpointEnabled b
jwtSigner: jwtSigner,
jar: mockJAR,
}
return &testCtx{
result := &testCtx{
ctrl: ctrl,
authnServices: authnServices,
policy: policyInstance,
Expand All @@ -1724,4 +1726,13 @@ func newCustomTestClient(t testing.TB, publicURL *url.URL, authEndpointEnabled b
client: client,
openid4vciClient: openid4vciClient,
}
// By default no OAuth client credentials are configured (preserving did:web + entity_id behavior). A test can set
// result.oauthClientCredentials to exercise the configured-client path; it matches by exact ServerURL.
authnServices.EXPECT().OAuthClientCredentials(gomock.Any()).DoAndReturn(func(authServerIssuer string) (*auth.OAuthClientConfig, bool) {
if result.oauthClientCredentials != nil && result.oauthClientCredentials.ServerURL == authServerIssuer {
return result.oauthClientCredentials, true
}
return nil, false
}).AnyTimes()
return result
}
21 changes: 18 additions & 3 deletions auth/api/iam/openid4vci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -137,7 +137,14 @@ func (r Wrapper) RequestOpenid4VCICredentialIssuance(ctx context.Context, reques
oauth.RedirectURIParam: redirectUri.String(),
oauth.CodeChallengeParam: pkceParams.Challenge,
oauth.CodeChallengeMethodParam: pkceParams.ChallengeMethod,
})
}
// EXPERIMENTAL: when client credentials are configured for this authorization server, present the configured
// client_id and drop the Nuts-specific entity_id client_id scheme (which external servers don't understand).
if clientConfig, ok := r.auth.OAuthClientCredentials(authzServerMetadata.Issuer); ok {
authzParams[oauth.ClientIDParam] = clientConfig.ClientID
delete(authzParams, oauth.ClientIDSchemeParam)
}
redirectUrl := nutsHttp.AddQueryParams(*authorizationEndpoint, authzParams)

return RequestOpenid4VCICredentialIssuance200JSONResponse{
RedirectURI: redirectUrl.String(),
Expand All @@ -155,8 +162,16 @@ func (r Wrapper) handleOpenID4VCICallback(ctx context.Context, authorizationCode
return nil, withCallbackURI(oauthError(oauth.ServerError, "missing wallet DID in session"), appCallbackURI)
}

// EXPERIMENTAL: when client credentials are configured for this authorization server, present the configured
// client_id and authenticate with the client_secret (client_secret_post) instead of the did:web client_id.
var clientSecret string
if clientConfig, ok := r.auth.OAuthClientCredentials(oauthSession.IssuerURL); ok {
clientID = clientConfig.ClientID
clientSecret = clientConfig.ClientSecret
}

// use code to request access token from remote token endpoint
tokenResponse, err := r.auth.IAMClient().AccessToken(ctx, authorizationCode, oauthSession.TokenEndpoint, checkURL.String(), *oauthSession.OwnSubject, clientID, oauthSession.PKCEParams.Verifier, false)
tokenResponse, err := r.auth.IAMClient().AccessToken(ctx, authorizationCode, oauthSession.TokenEndpoint, checkURL.String(), *oauthSession.OwnSubject, clientID, clientSecret, oauthSession.PKCEParams.Verifier, false)
if err != nil {
return nil, withCallbackURI(oauthError(oauth.AccessDenied, fmt.Sprintf("error while fetching the access_token from endpoint: %s, error: %s", oauthSession.TokenEndpoint, err.Error())), appCallbackURI)
}
Expand Down
Loading
Loading