55 · Principal Propagation

Implement OAuth2SAMLBearerAssertion flow to propagate BTP user identity to on-premise systems via Cloud Connector.

Episode 55 — Principal Propagation

What you'll learn

Why this matters

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.

What is principal propagation?

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)

The OAuth2SAMLBearerAssertion flow

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

Destination configuration (BTP Cockpit)

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

CAP implementation

// 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()
  }
}

Getting user token for propagation

// 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
})

Cloud Connector requirement

For on-premise S/4HANA, the OAuth2SAMLBearerAssertion destination requires:

  1. SAP Cloud Connector (SCC) installed on-premise
  2. SCC connected to BTP subaccount
  3. S/4HANA system exposed via SCC virtual host
  4. The destination uses proxyType: OnPremise with the virtual host
BTP ──SCC tunnel──► On-Premise S/4HANA

Troubleshooting

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

Hands-on exercise

  1. Create a destination with Authentication: OAuth2SAMLBearerAssertion in BTP Cockpit
  2. Connect it to an API sandbox (SAP API Business Hub provides test endpoints)
  3. Implement a handler that reads from the destination using CAP's destination service
  4. Log req.user.id and the outbound call to verify identity propagation

Checkpoint ✓

Sign in for progress tracking & Q&A →