Skip to content

Conversation

@davidkna-sap
Copy link
Member

@davidkna-sap davidkna-sap commented Dec 2, 2025

Handles SAP/ai-sdk-js-backlog#347.

@davidkna-sap davidkna-sap changed the title feat: IAS App-To-App Auth feat: [DO NOT MERGE] IAS App-To-App Auth Dec 2, 2025
@davidkna-sap davidkna-sap added the don't merge Don't merge label Dec 2, 2025
Copy link
Contributor

@marikaner marikaner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now I finally got through. Big PR => Many comments. Good work 😄

return options?.jwt?.app_tid;
}

requestAs satisfies never;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] What is the purpose of this line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should type-check that all possible values of requestAs are handled before reaching else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Thanks!

*/
app_tid?: string;
/**
* IAS tokens don't have scope property.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[q] Should it then be something like never or undefined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to stay compatible with the existing ClientCredentialsResponse, but considered that something of a breaking change (also XSUAA will likely always have this).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Comment on lines 143 to 147
let urn = `urn:sap:identity:application:provider:clientid:${resource.providerClientId}`;
if (resource.providerTenantId) {
urn += `:apptid:${resource.providerTenantId}`;
}
return urn;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I see let I get the urge to remove it. Not sure this is better though. Just an idea. Feel free to keep your version.

Suggested change
let urn = `urn:sap:identity:application:provider:clientid:${resource.providerClientId}`;
if (resource.providerTenantId) {
urn += `:apptid:${resource.providerTenantId}`;
}
return urn;
const resourceUrn = [resource.provderClientId, resource.providerTenantId].filter(val=>val).join(':apptid');
return `urn:sap:identity:application:provider:clientid:${resourceUrn}`;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it with a slightly different approach.

* @returns A promise resolving to the client credentials response.
* @internal
*/
async function getIasClientCredentialsTokenImpl(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[req] Every function is an implementation of something. The name currently sounds like we get some implementation. I assume this is not what is meant. Can we just remove the Impl?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name is fine getIasClientCredentialsToken is the inner implementation of getIasClientCredentialsToken which iirc calls it with additional resilience.

Copy link
Collaborator

@KavithaSiva KavithaSiva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please also add tests for getDestinationFromServiceBinding with the authentication type Oauth2JwtBearer.

Currently, only this method supports this authentication type, transformServiceBindingToDestination always creates a destination with Oauth2ClientCredentials. Could you also please confirm if the transform function for other services also always create a Oauth2ClientCredentials destination?

);
}

return buildIasDestination(response.access_token, service, options);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[req] I checked the flow again here and when iasOptions.authenticationType is set to OAuth2JWTBearer, the built IasDestination is still always one with OAuth2ClientCredentials which is incorrect as we do the access token retrieval using the jwt bearer token flow.

So, the authentication type OAuth2JWTBearer should be retained.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've addressed this by allowing authenticationType overrides for buildClientCredentials, and forwarding it in buildIasDestination.

@davidkna-sap
Copy link
Member Author

Only xfS4hanaCloudBindingToDestination uses BasicAuthentication instead of a client credentials.

Should I also forward the refresh token to the destination object if available? This would mean extending DestinationAuthToken. I did not do this right now because with the current flow (App-to-App), refresh tokens should always opted-out of, and they wouldn't be used.

I've also noticed that none of the Destinations with client-credentials set clientId or clientSecret, I avoided changing this to avoid further extending the scope of this PR.

@KavithaSiva
Copy link
Collaborator

Should I also forward the refresh token to the destination object if available? This would mean extending DestinationAuthToken. I did not do this right now because with the current flow (App-to-App), refresh tokens should always opted-out of, and they wouldn't be used.

I didn't understand this fully, in which API do you want to do this? Where is the refresh token available?

@davidkna-sap
Copy link
Member Author

Should I also forward the refresh token to the destination object if available? This would mean extending DestinationAuthToken. I did not do this right now because with the current flow (App-to-App), refresh tokens should always opted-out of, and they wouldn't be used.

I didn't understand this fully, in which API do you want to do this? Where is the refresh token available?

IdentityService.fetchJwtBearerToken may return a refresh token if refresh_expiry is not 0, which we do if either App-to-App is used (performance - unless the user opts) or if we have an app_tid (from the token or elsewhere which should always be the case).

I think the best place to put refresh tokens would be DestinationAuthToken, because they are tied to the main token.

@KavithaSiva
Copy link
Collaborator

I've also noticed that none of the Destinations with client-credentials set clientId or clientSecret, I avoided changing this to avoid further extending the scope of this PR.

I think that's fine because the Destination object is also used to represent destinations we create in CF, and these may have clientId and clientSecret depending on the Auth type.

Here we are creating destinations by transforming a service binding, I am not sure if they really add any value if set.

@KavithaSiva
Copy link
Collaborator

IdentityService.fetchJwtBearerToken may return a refresh token if refresh_expiry is not 0, which we do if either App-to-App is used (performance - unless the user opts) or if we have an app_tid (from the token or elsewhere which should always be the case).

I think the best place to put refresh tokens would be DestinationAuthToken, because they are tied to the main token.

Understood, let's do this in the next IAS follow-up ticket, which may need and use the refresh token.

Copy link
Collaborator

@KavithaSiva KavithaSiva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last round of minor comments to address and then it's good to go.
Code looks much cleaner and readable now, thanks for all the effort and good work.

Copy link
Collaborator

@KavithaSiva KavithaSiva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's go!

@davidkna-sap davidkna-sap enabled auto-merge (squash) January 26, 2026 11:32
@davidkna-sap davidkna-sap disabled auto-merge January 26, 2026 11:32
@davidkna-sap davidkna-sap enabled auto-merge (squash) January 26, 2026 11:32
@marikaner marikaner dismissed their stale review January 27, 2026 10:08

as discussed

@davidkna-sap davidkna-sap merged commit d444438 into main Jan 27, 2026
26 checks passed
@davidkna-sap davidkna-sap deleted the davidkna-sap_poc-ias branch January 27, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants