Outbound Authentication
Remote Service Abstraction
According to the key concept of pluggable building blocks, CAP's Remote Services architecture decouples the protocol level (exchanged content) from the connection level (established connection channel). While the business context of the application impacts the protocol, the connectivity of the service endpoints is independent of it and mainly depends on platform-level capabilities. The latter is frequently subject to change and therefore should not introduce application dependencies.
At the connectivity layer, the following basic tasks can be addressed generically:
- Authentication (how to set up a trusted channel)
- Destination (how to find the target service)
- User propagation (how to transport user information)
CAP's connectivity component handles authentication (IAS, XSUAA, X.509, ZTID, ...), destination (local destination, BTP Destination, BTP Service Binding), and user propagation (technical provider, technical subscriber, named user) transparently through configuration (although the configuration approach may differ between Java and Node.js). All three service scenarios can be addressed through configuration variants of the same remote service concept, as shown in the following sections.
CAP supports out-of-the-box consumption of various types of remote services:
- Co-located services as part of the same deployment and bound to the same identity instance (that is, belong to the same trusted application zone).
- External services that can be running on non-BTP platforms.
Co-located Services
Co-located services do not run in the same microservice, but are typically part of the same deployment unit and hence reside within the same trust boundary of the application zone. Logically, such co-located services contribute to the application equally and could run as integrated services in the same microservice, but for technical reasons (for example, different runtime or scaling requirements) they are separated physically, often as a result of a late-cut microservice approach.
Technically, they share the same identity instance, which allows direct token forwarding:
Learn more about how to configure co-located services in CAP JavaLearn more about how to configure remote services in CAP Node.js
You can test CAP's built-in support for co-located services in practice by modifying the sample applications:
- Java:
xflights-javaandxtravels-java - Node.js:
xflightsandxtravels
xflights acts as a master data provider exposing basic flight data in service sap.capire.flights.data via different protocols. On the client side, xtravels imports this service as a CAP remote service and fetches data for federation.
CAP offers
A simplified co-located service setup by leveraging remote services that require:
- Shared identity instance
- URL for the destination
- Principal propagation mode (optional)
To combine both applications in a co-located setup, follow these steps:
1. Prepare the Cloud Foundry Environment
Make sure that you've prepared a local environment for CF deployments and in addition:
- A Cloud Foundry (CF) space in a subaccount.
- HANA Cloud instance mapped to the CF space.
- IAS tenant mapped to the subaccount.
2. Prepare and Deploy the Consumer Application
As client, xtravels first needs a valid configuration for the remote service sap.capire.flights.data:
---
spring:
config.activate.on-profile: cloud
cds:
remote.services:
xflights:
type: hcql
model: sap.capire.flights.data
http:
suffix: /hcql
binding:
name: xtravels-ias
onBehalfOf: systemUser
options:
url: https://<xflights-srv-cert url>{
"cds": {
"requires": {
"sap.capire.flights.data": {
"kind": "hcql",
"[production]": {
"credentials": {
"url": "https://<xflights-srv-cert url>/hcql/data",
"forwardAuthToken": true
}
}
}
}
}
}Java configuration explained
The type property activates the protocol for exchanging business data and must be offered by the provider CDS service. The model property needs to match the fully qualified name of the CDS service from the imported model. You can find CDS service definition of sap.capire.flights.data in file target/cds/capire/xflight-data/service.cds resolved during CDS build step. The binding.name needs to point to the shared identity instance and options.url together with http.suffix provides the required location of the remote service endpoint. Finally, onBehalfOf: systemUser specifies that the remote call is invoked on behalf of a technical user in context of the tenant.
Tip
On behalf of systemUser (Java) works both in pure single tenant and in pure multitenant scenarios. If you are consuming a single tenant service from within a multitenant application choose on behalf of systemUserProvider.
Node.js configuration explained
The configuration follows the standard pattern for required services with service bindings.
For co-located services sharing the same identity instance, forwardAuthToken: true forwards the incoming JWT directly to the provider - no token exchange needed since the token is already valid. Unlike Java's onBehalfOf option, no additional configuration is required as the original user context is preserved in the forwarded token.
Now you are ready to deploy the application with
cd ./xtravels-java
cds upcd ./xtravels
cds up❗Note that CF application xtravels-srv will not start successfully as long as xflights is not deployed yet (step 3).
Tip
For production deployment, we recommend combining both services with the shared identity instance in a single MTA descriptor.
3. Prepare and Deploy the Provider Application
As server, xflights needs to restrict service sap.capire.flights.data to the technical client calling from the same application. This can be done by adding pseudo-role internal-user to the service:
using { sap.capire.flights.data as data } from './data-service';
annotate data with @(requires: 'internal-user');Tip
For different user propagation modes the remote service can be configured appropriately. The provider service authorization needs to align with the configured user propagation.
Additionally, to establish the co-located setup, the microservice needs to share the same identity instance. This is configured in the MTA deployment descriptor:
The
mta.yamlhas been generated bycds up.
resources:
- name: xflights-ias
type: org.cloudfoundry.managed-service
type: org.cloudfoundry.existing-service
parameters:
service: identity
service-name: xflights-ias
service-name: xtravels-ias
service-plan: application
config:
display-name: xflightsFinally, deploy and start the application with
cd ./xflights-java
cds upcd ./xflights
cds up4. Verify the Deployment
First, you can check the overall deployment status at the CF CLI level. Specifically, the application services must be started successfully and the shared identity instance must be verified.
Verify: cf apps should show the following lines:
name requested state processes routes
xflights-db-deployer stopped web:0/1
xflights-srv started web:1/1 ...
xtravels started web:1/1 ...
xtravels-ams-policies-deployer stopped web:0/1
xtravels-db-deployer stopped web:0/1
xtravels-srv started web:1/1 ...Verify: cf services should show the following lines:
xflights-ias identity application
xtravels-ias identity application xtravels, xtravels-srv, xflights-srv, ...You can test the valid setup of the xtravels application by accessing the UI and logging in with an authorized test user of the IAS tenant. To do so, assign a proper AMS policy (for example, admin) to the test user as described in CAP-level Users and Roles.
Tip
The very same setup could be deployed for XSUAA-based services.
External Services
In contrast to co-located services, external services do not have strong dependencies as they have a fully decoupled lifecycle and are provided by different owners. As a consequence, external services can run cross-regionally; even non-BTP systems might be involved. A prerequisite for external service calls is a trust federation between the consumer and the provider system.
A seamless integration experience for external service communication is provided by IAS App-2-App flows, which are offered by CAP via remote services. BTP Destinations offer various authentication strategies such as SAML 2.0 as required by many S/4 system endpoints. Both CAP Java and CAP Node.js support IAS App-2-App via configuration to handle token exchange automatically - Java uses service bindings with ias-dependency-name, while Node.js uses BTP Destinations with tokenService.body.resource.
IAS App-2-App
As a first-class citizen, IAS is positioned to simplify cross-regional requests with user propagation. Prerequisites are identity instances on both consumer and provider sides, plus a registered IAS dependency in the consumer instance.
CAP supports communication between arbitrary IAS endpoints and remains transparent for applications as it builds on the same architectural pattern of remote services. Technically, the connectivity component uses IAS App-2-App flows in this scenario that requires a token exchange from a consumer token into a token for the provider. The latter is issued by IAS only if the consumer is configured with a valid IAS dependency pointing to the provider accordingly.
Tip
CAP offers App-2-App setup by leveraging remote services that require:
- Identity instances for provider and consumer
- Configured IAS dependency from consumer to provider
- URL pointing to the provider
- Principal propagation mode (optional)
Learn more about how to consume external application APIs with IAS
1. Prepare and Deploy the Provider Application
Assuming the same local CF environment setup as here, clone the sample application (xflights-java or xflights for Node.js), or if already cloned and modified locally, reset to the remote branch.
Similar to the co-located variant, xflights needs to expose service sap.capire.flights.data to technical clients. The difference is that the consumers are not known a priori and are not part of the same application deployment.
To expose service APIs for consumption, you can enhance the identity instance of the provider by defining API identifiers that are listed in property provided-apis:
resources:
- name: xflights-ias
type: org.cloudfoundry.managed-service
parameters:
service: identity
service-plan: application
config:
display-name: xflights
oauth2-configuration:
token-policy:
access-token-format: jwt
provided-apis:
- name: data-consumer
description: Grants technical access to data service APIThe entry with name data-consumer represents the consumption of service sap.capire.flights.data and is exposed as IAS API. The description helps administrators to configure the consumer application with the proper provider API if done on UI level.
How can proper authorization be configured for technical clients without user propagation?
OAuth tokens presented by valid consumer requests from an App-2-App flow will have API claim data-consumer, which is automatically mapped to a CAP role by the runtime.
Therefore, you can protect the corresponding CDS service by CAP role data-consumer to authorize requests thoroughly:
using { sap.capire.flights.data as data } from './data-service';
annotate data with @(requires: 'data-consumer');For Node.js, additionally configure the authentication strategy in package.json:
{
"cds": {
"requires": {
"auth": {
"[production]": {
"kind": "ias"
}
}
}
}
}Finally, deploy and start the application with
cd ./xflights-java
cds upcd ./xflights
cds upAPI as CAP role
The API identifiers exposed by the IAS instance in list provided-apis are granted as CAP roles after successful authentication and can be used in @requires annotations.
Use different roles for technical and business users
Use different CAP roles for technical clients without user propagation and for named business users.
Instead of using the same role, expose dedicated CDS services to technical clients that are not accessible to business users and vice versa.
2. Prepare and Deploy the Consumer Application
Like the provider application (xflights), clone the sample application (xtravels-java or xtravels for Node.js), or if already cloned and modified locally, reset to the remote branch.
The remote service can be configured in a very similar way as with co-located services. You only need to add the information about the IAS dependency to be called. The name for the IAS dependency is flexible but needs to match the chosen name in the next step when connecting consumer and provider in IAS.
spring:
config.activate.on-profile: cloud
cds:
remote.services:
xflights:
type: hcql
model: sap.capire.flights.data
http:
suffix: /hcql
binding:
name: xtravels-ias
onBehalfOf: systemUser
options:
url: https://<xflights-srv-cert url>
ias-dependency-name: data-consumer{
"cds": {
"requires": {
"auth": {
"[production]": {
"kind": "ias"
}
},
"sap.capire.flights.data": {
"kind": "hcql",
"[production]": {
"credentials": {
"path": "/hcql/data",
"destination": "xflights-ias-app2app"
}
}
}
}
}
}Java configuration explained
The ias-dependency-name property configures the IAS App-2-App flow directly in application.yaml. This is all that's needed for Java - the CAP Java runtime handles the token exchange automatically.
Node.js: Configure a BTP Destination that handles the IAS token exchange. The destination references the IAS dependency name, which must match the name used when connecting consumer and provider in IAS.
MTA cannot resolve cross-service credential references
The destination must be created manually in BTP Cockpit or via Destination Service API, as MTA cannot reference IAS credentials like ${generated>xtravels-ias/clientid}.
Node.js configuration explained
CAP Node.js supports IAS App-2-App via BTP Destinations using standard remote service configuration.
1. Configuration - Use a named destination in credentials:
path: The HCQL endpoint path on the providerdestination: Name of the BTP Destination configured for IAS App-2-App
2. BTP Destination - Create a destination in BTP Cockpit with these properties:
| Property | Value |
|---|---|
| Name | xflights-ias-app2app |
| Type | HTTP |
| URL | https://<xflights-srv url> |
| Proxy Type | Internet |
| Authentication | OAuth2ClientCredentials or OAuth2JWTBearer (see below) |
| Client ID | Consumer IAS client ID |
| Client Secret | Consumer IAS client secret |
| Token Service URL | https://<IAS tenant>/oauth2/token |
| Token Service URL Type | Dedicated |
Client ID and Client Secret are obtained from the consumer's IAS service key:
cf create-service-key xtravels-ias xtravels-ias-keyAdditional Property (required for IAS App-2-App):
| Property | Value |
|---|---|
tokenService.body.resource | urn:sap:identity:application:provider:name:data-consumer |
The key is tokenService.body.resource which passes the resource parameter to IAS, triggering App-2-App token scoping and adding the ias_apis claim.
3. IAS App-2-App supports two authentication types:
- OAuth2ClientCredentials: For technical user scenarios (no user context)
- OAuth2JWTBearer: For user propagation (requires user token from authorization_code flow)
Both support tokenService.body.resource for IAS App-2-App scoping.
4. MTA descriptor - bind the IAS and Destination services:
modules:
- name: xtravels-srv
requires:
- name: xtravels-ias
- name: xtravels-destination
resources:
- name: xtravels-ias
type: org.cloudfoundry.managed-service
parameters:
service: identity
service-plan: application
config:
display-name: xtravels
oauth2-configuration:
token-policy:
access-token-format: jwt
- name: xtravels-destination
type: org.cloudfoundry.managed-service
parameters:
service: destination
service-plan: liteFinally, deploy and start the application with
cd ./xtravels-java
cds upcd ./xtravels
cds upxtravels-srv is not expected to start successfully; instead, you should see error log messages like this:
Remote HCQL service responded with HTTP status code '401', ...You solve this in the next step, by connecting the consumer and the provider application.
Technically, the App-2-App flow takes the token from the request and triggers...
... an IAS token exchange for the target IAS dependency. In Java, CAP's remote service handles this automatically. In Node.js, the BTP Destination with tokenService.body.resource triggers the token exchange via the Destination Service. As the IAS dependency is not created yet, IAS rejects the token exchange request and the call to the provider fails with 401 (not authenticated).
Note that property oauth2-configuration.token-policy.access-token-format: jwt is set in the identity instance to ensure the exchanged token has JWT format.
3. Connect Consumer with Provider
Now create the missing IAS dependency to establish trust for the API service call targeting the provided API with ID data-consumer.
Open the Administrative Console for the IAS tenant, check prerequisites in Authentication:
Select Applications & Resources > Applications. Choose the IAS application of the
xtravelsconsumer from the list.In Application APIs select Dependencies and click on Add.
Type
data-consumeras dependency name and pick provided APIdata-consumerfrom the provider IAS applicationxflights.The dependency name needs to match property value
ias-dependency-namein Java, or the name suffix in thetokenService.body.resourceURN for Node.js, for example,urn:sap:identity:application:provider:name:<dependency-name>wheredata-consumeris the dependency name.Confirm with Save
Create IAS dependency in Administrative Console


Now restart the consumer application:
cf restart xtravels-srvThis triggers a successful startup with valid flight data retrieved from the provider.
You can now test the valid setup of the xtravels application by accessing the UI and logging in with an authorized test user of the IAS tenant. To do so, assign a proper AMS policy (for example, admin) to the test user as described earlier.
Pitfalls
Don't write custom integration logic for consumed services. Leverage CAP's remote service architecture instead to ensure a seamless integration experience.
Don't implement connectivity layer code (for example, to fetch or exchange tokens). Instead, rely on the shared connectivity component, which ensures centralized and generic processing of outbound requests.
Don't treat co-located services as external services. This introduces unnecessary communication overhead and increases total cost of ownership.