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

on

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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s