Implement OAuth2SAMLBearerAssertion flow to propagate BTP user identity to on-premise systems via Cloud Connector.
When a CAP app calls an on-premise SAP system (ECC, S/4HANA on-prem), the SAP backend needs to know who the user is — not just that a service is calling. Principal propagation carries the BTP user's identity into the on-prem system so audit logs and authorizations work correctly.
Without propagation:
Alice → CAP (alice's JWT) → S/4HANA (service user "CAP_SERVICE")
S/4HANA sees: all requests come from CAP_SERVICE — no user context
With propagation:
Alice → CAP (alice's JWT) → exchange for S/4HANA SAML assertion → S/4HANA
S/4HANA sees: request from ALICE (her on-prem user ID)
1. CAP receives Alice's BTP JWT (issued by XSUAA)
2. CAP calls XSUAA: "I have Alice's JWT, give me a SAML assertion for alice@company.com"
3. XSUAA issues SAML assertion (short-lived, signed)
4. CAP presents the SAML assertion to S/4HANA's OAuth endpoint
5. S/4HANA exchanges SAML for an access token for alice's on-prem account
6. CAP uses that access token to call S/4HANA APIs as Alice
In BTP Cockpit → Connectivity → Destinations → New:
Name: S4HANA_PP
Type: HTTP
URL: https://s4hana.company.com
Authentication: OAuth2SAMLBearerAssertion
Client ID: <XSUAA client ID>
Client Secret: <XSUAA client secret>
Token Service URL: https://<subdomain>.authentication.eu10.hana.ondemand.com/oauth/token/alias/<subdomain>
User Header Name: X-SCI-PRINCIPAL-EMAIL (varies by S/4HANA version)
AutoTokenRetrieval: On
// srv/s4-integration.js
const cds = require('@sap/cds')
module.exports = class S4IntegrationService extends cds.ApplicationService {
async init () {
const destination = await cds.connect.to('destination-service')
const s4 = await cds.connect.to('S4HANA_PP') // configured destination
this.on('READ', 'MyOrders', async (req) => {
// CAP uses the destination's OAuth2SAMLBearerAssertion config
// and automatically exchanges Alice's JWT before calling S/4HANA
return s4.run(
`GET /sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder`,
{ headers: { 'SAP-Client': '100' } }
)
})
await super.init()
}
}
// When you need explicit control
this.on('callS4WithUserContext', async (req) => {
const { tokenInfo } = req.user
const userToken = tokenInfo?.getTokenValue()
if (!userToken) return req.reject(401, 'No user token available for propagation')
const dest = await cds.connect.to('destination-service')
const options = await dest.getDestination('S4HANA_PP', {
userTokenExchangeEnabled: true,
userToken
})
const response = await axios.get(`${options.url}/sap/opu/odata/...`, {
headers: { ...options.authorizationHeaders }
})
return response.data
})
For on-premise S/4HANA, the OAuth2SAMLBearerAssertion destination requires:
proxyType: OnPremise with the virtual hostBTP ──SCC tunnel──► On-Premise S/4HANA
| Symptom | Cause | Fix |
|---|---|---|
| 401 on S/4HANA | SAML trust not configured | Configure S/4HANA OAuth client for XSUAA issuer |
| 403 on S/4HANA | User not in S/4HANA system | Create user mapping or sync from IAS |
| SAML assertion expired | Token exchange took too long | Check network latency, reduce steps |
| "Principal not found" | Username mapping mismatch | Check User Header Name in destination config |
Authentication: OAuth2SAMLBearerAssertion in BTP Cockpitreq.user.id and the outbound call to verify identity propagationAuthentication: OAuth2SAMLBearerAssertion handle the exchange automatically