cancel
Showing results for 
Search instead for 
Did you mean: 

CAP CDS8 Remote Services with Principal Propagation fails?

carlonnheim
Participant
358

Hi,

We have a project running on CAP CDS7 which uses remote services in handlers like this

this.on('READ', 'Something', async req => {
    const svc = await cds.connect.to('AnotherService');
    const res = await svc.run(SELECT.from('RemoteThing', someId));
    return res;
})

We are now trying to upgrade to CDS8 and all such delegated calls fail with errors like this:

Error during request to remote service: Failed to load destination. Caused by: No user token (JWT) has been provided. This is strictly necessary for 'OAuth2JWTBearer'.

Stepping through the remote requests we find that it fails in node_modules/@sap/cds/libx/_runtime/remote/Service.js. The authorization headers are no longer present in req.context.headers (only a single 'x-correlation-id' header remains there). The actual authorization header is in req.context.http.req.headers.authorization

    // IMPORTANT: regular function is used on purpose, don't switch to arrow function.
    this.on('*', async function on_handler(req, next) {
      const { query } = req
      if (!query && !(typeof req.path === 'string')) return next()

      // early validation on first request for use case without remote API
      // ideally, that's done on bootstrap of the remote service
      if (typeof this.destination === 'object' && !this.destination.url)
        throw new Error(`"url" or "destination" property must be configured in "credentials" of "${this.name}".`)

      const reqOptions = getReqOptions(req, query, this)
      reqOptions.headers = _setHeaders(reqOptions.headers, req)

      const { kind, destination, destinationOptions } = this
      const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
      const returnType = req._returnType
      const additionalOptions = { destination, kind, resolvedTarget, returnType, destinationOptions }

      // req.context.headers only contains 'x-correlation-id' after the upgrade, no jwt token is forwarded
      // const jwt = req?.context?.headers?.authorization?.split(/^bearer /i)[1]
      const jwt = req?.context?.http?.req?.headers?.authorization?.split(/^bearer /i)[1] // ---> Changing like this makes it work?
      if (jwt) additionalOptions.jwt = jwt

      // hidden compat flag in order to suppress logging response body of failed request
      if (req._suppressRemoteResponseBody) {
        additionalOptions.suppressRemoteResponseBody = req._suppressRemoteResponseBody
      }

      let result = await run(reqOptions, additionalOptions)
      result = typeof query === 'object' && query.SELECT?.one && Array.isArray(result) ? result[0] : result
      return result
    })

Is there something we need to change in our application code with CDS8, or is this a bug? We tried for example making explicit "svc.tx(req).run(...)" which we have usually not had to do but that did not make any difference.

Thanks in advance for any advice!

//Carl

LuizGomes
Participant
0 Kudos
I have the same problem, I believe it is a bug, now I am wondering which package needs to be reverted and to which version.
LuizGomes
Participant
0 Kudos
I suggest you open an issue here: https://github.com/cap-js/docs/issues
View Entire Topic
carlonnheim
Participant

Hi @LuizGomes ,

We use a "patch-package" for now, with a patch file like that. Not a long term solution but works until we get this clarified.

diff --git a/node_modules/@sap/cds/libx/_runtime/remote/Service.js b/node_modules/@sap/cds/libx/_runtime/remote/Service.js
index 04e0929..a98bfcf 100644
--- a/node_modules/@sap/cds/libx/_runtime/remote/Service.js
+++ b/node_modules/@sap/cds/libx/_runtime/remote/Service.js
@@ -261,7 +261,7 @@ class RemoteService extends cds.Service {
       const returnType = req._returnType
       const additionalOptions = { destination, kind, resolvedTarget, returnType, destinationOptions }
 
-      const jwt = req?.context?.headers?.authorization?.split(/^bearer /i)[1]
+      const jwt = req?.context?.headers?.authorization?.split(/^bearer /i)[1] || req?.context?.http?.req?.headers?.authorization?.split(/^bearer /i)[1]
       if (jwt) additionalOptions.jwt = jwt
 
       // hidden compat flag in order to suppress logging response body of failed request

Hope it helps

//Carl

LuizGomes
Participant
LuizGomes
Participant
0 Kudos
I did the patch and it worked for me. Thanks for sharing.