Skip to Content
Technical Articles
Author's profile photo EDGAR MARTINEZ

Extending App Router (Node.js) for: SAP Principal Propagation in Cloud Foundry to an On-premise system via User Exchange Token.

Edit 18/04/2021: For anyone looking around, The SAP App router supports principal propagation by default, thanks @gregor.wolf for pointing that out. That transforms the following blog post into a didactic exercise on how to extend the app router functionality 🙂 Have fun!

A brief overview of Principal propagation

In Cloud Foundry, Principal propagation is the process used to “forward the identity of a cloud user to a remote system. This process is called principal propagation (also known as user propagation or user principal propagation).”
SAP BTP Connectivity (Principal Propagation)

When we talk about reaching a remote system in this context, we are talking about an on-premise system (ECC, S/4 HANA, for example).

Also, we would typically consume a service in the backend for this scenario (commonly oData or RFC). To access this service, our users would authenticate to the Cloud Platform directly, then the corresponding remote system calls (or backend calls) would be made. Once one of these remote calls is made, the user authentication information would be sent from the Cloud to the backend system via the Cloud Connector. Then, the back-end system would validate the call on the back-end user’s context, meaning that the corresponding user authorizations (authorization objects/roles) in the backend would be granted for the service execution.

On the technical side, this flow (User Exchange Token) can be seen as a credential exchange, using the JWT token(s) to authenticate users on the Cloud side, then sending the corresponding token to the Cloud Connector along with the backend call. Then, this JWT token is used in the Cloud connector to generate a temporary x509 certificate which ultimately is used to authenticate the user in the backend.

This flow is detailed in the documentation as well as in different blogs from the community:

 

Principal propagation with Node.js ( Configure Principal Propagation via User Exchange Token ).

Speaking about the technical implementation of this User Exchange Token flow, a slim client library to execute Principal Propagation can be found for Java. The objective of this blog is to achieve this functionality by using the App Router in Node.js.

Taking the documentation description, here is a GitHub repository with a sample MTA application.
The App Router (The component in MTA applications in charge of forwarding or proxy requests, among other functionality) in this application has been extended to intercept particular routes (in our example, routes containing /sap). Once a call is made by an application to a route containing this prefix (for example, a Fiori application requesting metadata from an OData service as in the repo example), the extension code executes the JWT exchange flow, so it adds the required JWT token to the request, and the Principal propagation scenario is achieved.

Next, here is a brief of the code to achieve this scenario:

var approuter = require('@sap/approuter');
var axios = require('axios');
var FormData = require('form-data');

var ar = approuter();

const oVCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES);
const oConnectivityServiceCredentials = oVCAP_SERVICES.connectivity[0].credentials;

// Intercept requests going to /sap route (backend call) to add JWT Exchange Token
 ar.beforeRequestHandler.use('/sap', async function myMiddleware(req, res, next) {

    if (req.url.indexOf("/sap") > -1) {   /// Review the route contains /sap TODO: Can be extended to use regular expression
        try {

            var oAuthorization = JSON.parse(req._passport.session.user);   // Obtain Authorization object from request
            // The original JWT token can be found at oAuthorization.token.accessToken;  

            /// Make the post call according to the documentation so we can obtain JWT Exchange Token
            var params = new URLSearchParams();
            params.append('client_id', oConnectivityServiceCredentials.clientid);
            params.append('client_secret', oConnectivityServiceCredentials.clientsecret);
            params.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
            params.append('token_format', 'jwt');
            params.append('response_type', 'token');
            params.append('assertion', oAuthorization.token.accessToken);  /// Send original JWT token to connectivity service

            var response = await axios({
                method: "post",
                url: oConnectivityServiceCredentials.token_service_url + "/oauth/token",
                params: params,
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded",
                    "Accept": "application/json"
                }
            });

            var userExchangeToken = response.data.access_token;

            req.headers('Proxy-Authorization', userExchangeToken);   // Add JWT Exchange token to the original request

            next();    /// Release the request with the new header

        } catch (error) {

            console.log(error);
            res.end(JSON.stringify(error));
        }
        
    } else {
        next();
        return;
    }


});
ar.start();

I hope you find this code useful whenever you want to implement Principal propagation via User Exchange token flow in your Cloud Foundry projects.

Thanks!

Assigned tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Edgar,

      can you go in bit more detail to describe the usecase or this approuter extension? It's not getting clear to me when I would apply that.

      Also please check out @sap/xsenv for a more robust way to read the services from VCAP_SERVICES environment variable.

      Best regards
      Gregor

      Author's profile photo EDGAR MARTINEZ
      EDGAR MARTINEZ
      Blog Post Author

      Hi Gregor,

      Sure thing, I will take a look at @sap/xsenv and integrate it into the project.

      Our use case started with a project that required a standalone Fiori application that could leverage Principal Propagation. We had a look around, and in the end, we came along with this solution. I believe the code could be reused in projects where you need to have an independent App router and the need to use Principal propagation. I think it might be refactored a bit to be used in NodeJS projects running in CF in general. I imagine a couple of possible use cases where I hope it can be useful 🙂

      Thank you!

      Best!

      Edgar.

      Author's profile photo Gregor Wolf
      Gregor Wolf

      Hi Edgar,

      but the @sap/approuter does already support principal propagation I'm using that functionality in the approver of my projext HTML5UserAPIforCF. Can you explain what was not working for you?

      Best regards
      Gregor