Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
gustavokath
Product and Topic Expert
Product and Topic Expert

One of the challenges of data-driven decision making is to present complex and diverse data in a way that is easy to access and understand to act upon. SAP Datasphere delivers seamless and scalable access to mission-critical business data from multiple sources, accessing this data everywhere has the power to ease decision making and speed up business processes. In this context, SAP Build plays vital role to accelerate the apps development and making data accessible on the go. In this blog post, you will learn how to create an SAP Build app that integrates with SAP Datasphere as a centralized data source. This is achieved through the use of SAP Datasphere Consumption oData APIs. SAP Build benefits from SAP Datasphere's authentication model using a centralized Identity Provider (IdP) to grant access to the app and its contents. The blog post will guide you through the following steps:  

  1. Understanding the authentication flow
  2. Developing the BTP Authorization Backend
  3. Creating OAuth Client & Destination
  4. Modeling an SAP Datasphere view to be used on SAP Build
  5. Configuring SAP Build Authentication
  6. Setup SAP Build App  Data Model

gustavokath_0-1708796554511.png

1. Understanding the authentication flow

The most complex and crucial step in integrating these two systems is the authentication process. SAP Datasphere Consumption APIs necessitate a three-legged OAuth2.0 flow with the "Authorization Code" grant type, which needs manual user interaction with the configured Identity Provider (IdP). For more information on the protocol, please refer to our reference page here.

However, SAP Build does not natively support this type of authentication, neither using SAP BTP authentication type. The solution for such problem can accomplished by starting the App login step in a browser based view (aka: WebView) in the App opening to the SAP Datasphere authorization URL. This will initiate communication with the configured IdP in your SAP Datasphere (such as SAP ID, Office 365, etc), displaying its login page. Upon successful provision of user credentials, the OAuth Client redirect URI will be invoked.

For the entire process to operate securely and correctly, the OAuth Client redirect URI must be configured for the backend application API. Within the scope of this blog post, a BTP application will be created for this specific purpose. This backend application will utilize the OAuth Client ID and secret to generate the SAP Datasphere access token, which will then be sent back to the SAP Build Application.

The image bellow illustrates this flow:
auth_model.png

This authentication process is inspired on the one described in the SAP Build Samples GitHub. To avoid the Client ID and Secret to be stored on device the "Authorization Backend" was introduced, so sensitive information could be stored into the BTP Destination Service.

2. Developing the BTP Authorization Backend

The "Authorization Backend" was introduced to ensure sensitive information is securely stored on the server. This blog post will detail the deployment of a new BTP application in NodeJS as an example, but the same steps can be applied to any new or existing backend service running on BTP or in another cloud environment.  

The application need to realize 3 main tasks:

  1. Receive the redirect URI API call and extract the authorization code
  2. Handle OAuth2.0 token request
  3. Return the access token to the client

2.1. Receive the redirect URI API call and extract the authorization code

Receiving the Redirect URI call in the service requires exposing an unauthenticated route that matches the path used in the OAuth Client configuration (Step 3.1). In this example "/oauth/callback" (<callback-api-path>) will be used.

The authorization code, generated once the IdP login is successful, will be received as the "code"  query parameter on the callback route, it will be used later for OAuth token request.

Redirect URL Example:

https://auth-backend.hana.ondemand.com/oauth/callback?code=lxuC1347ZLcNg3gjqFHKjAc6gT4iB0hp

NodeJS Code sample:
[File: index.js]

 

 

import express, { Request, Response } from "express";
const port = process.env.PORT || 3000;
const app = express();

app.get("/oauth/callback", async (req: Request, res: Response
  try {
    const code = req.query.code as string;
  } catch (e) {
    res.status(500).json(e.message);
  }
});

app.listen(port, () => {
console.log("Server listening on " + port);
});

 

 

2.2 Handle OAuth2.0 token request

After obtaining the authorization code, the next step in the OAuth protocol is requesting the access token. On this blog post the SAP BTP Destination Service will be used for storing the OAuth Client ID and Secret, as well as for requesting the access token. Some dependencies to this process such as BTP destination and Client ID and Secret will be explained and later created on section 3.

The process for requesting the access token uses the "Find Destination" API from SAP BTP Destination Service. It requires the authorization code received earlier, the destination name (set up as an environment variable), It is crucial that the BTP Application is linked with the destination service. Instructions for these steps are also detailed on the SAP Help pages [1][2][3], used as reference for this blog post.

The outcome of this request is the access and refresh tokens which will be used for authenticating all Consumption API request. The code snippet bellow shows how to configure such request in as part of the App created.

[File: destination-service.js]

 

 

import * as xsenv from "@sap/xsenv";
import axios from "axios";
export const findDestination = async (
  destinationName: string,
  code: string,
  redirectHost: string
) => {
  const credentials = xsenv.cfServiceCredentials({ tag: "dest
  const token = await getDestinationServiceToken();
  const response = await axios({
    method: "get",
    url:`${credentials.uri}/destination-configuration/v1/des
    headers: {
      Authorization: `Bearer ${token}`,
      "X-code": code,
      "X-redirect-uri": redirectHost,
    },
  });

  if (!response.data.authTokens || response.data.authTokens.l
    throw new Error(`Failed to fetch destination, ${response.
  }

  const tokenObject = response.data.authTokens[0];
  if (tokenObject.error) {
    throw new Error(`Failed to fetch destination, ${tokenObje
  } else if (tokenObject.expires_in !== "0") {
    delete tokenObject["http_header"];
    return tokenObject;
  }
  throw new Error(`Failed to fetch destination, invalid token
};

const getDestinationServiceToken = async () => {
  const credentials = xsenv.cfServiceCredentials({ tag: "dest
  const response = await axios({
    method: "post",
    url:`${credentials.url}/oauth/token`,
    headers: {
      Authorization: `Basic ${Buffer.from(`${credentials.clientid}:${credentials.clientsecret}`).toString("base64")}`,
      "Content-Type": "application/x-www-form-urlencoded",
  },
    data: {
      client_id: credentials.clientid,
      grant_type: "client_credentials",
    },
  });

  if (response.status !== 200) {
    throw new Error(`Failed to fetch destination service access token, ${req});
  }

  return response.data.access_token;
};

 

 

2.3. Return the access token to the client

The last responsibility of the backend application is to return the token back to the client (SAP Build App), which started the authentication flow, in a way it could read and use. In the context of SAP Build Apps which will be using a web browser for all authentication process a straightforward approach is for the server to redirect the request to another URL it has (/oauth/token) in this blog post and provide the access token as part of the query parameters (as implemented in this blog) or within the body.

Some adjustments are required in the index.js file for calling the methods created above

[File: index.js]

 

 

import express, { Request, Response } from "express";
const { findDestination } = require("./destination-service");
const port = process.env.PORT || 3000;
const app = express();

app.get("/oauth/callback", async (req: Request, res: Response
  try {
    const code = req.query.code;
    const destinationName = process.env.DESTINATION_NAME;
    const redirectUri = `https://${req.hostname}/oauth/callba
    const datasphereToken = await findDestination(destinationName,code,redirectUri);
    res.redirect(`/oauth/token?access_token=${datasphereToken.value}`);
  } catch (e) {
    res.status(500).json(e.message);
  }
});

app.get("/token", async function (req, res) {
  res.status(200).send();
});

 

 

This is an example of how a BTP backend application can be developed to serve as authentication middleware between SAP build, functioning as a client, and the destination service in BTP. The only step missing is to deploy the application to BTP and use it - follow BTP guidance on how to deploy an application.

For the next step we will need some information created on this section save for later:

  • Host of your BTP application - referenced as <backend-app-host>
  • Callback API Path - referenced as <callback-api-path>

3. Creating OAuth Client & Destination

The first thing to be realized for allowing integration with SAP Datasphere Consumption APIs involves creating an OAuth Client within the SAP Datasphere. This action generates the "ClientID" and "Client Secret" necessary for subsequent steps.

3.1. Create an OAuth Client in SAP Datasphere

  1. Go to your SAP Datasphre Administration page
    settings.png
  2. Open “App Integration” tab
  3. Create a New OAuth Client to be used later
    1. Set the name of your OAuth Client
    2. In Purpose field set “Interactive Usage”
    3. In Redirect URI field set the URL of the callback API created on section 2, it should be <backend-app-host>/<callback-api-path>
      client.png

       

  For the next step we will need some information created on this section save for later:

  • Client ID - referenced as <client-id>
  • Client Secret - referenced as <client-secret>
  • SAP Datasphere Authorization URL - referenced as <datasphere-authorization-url>
  • SAP Datasphere Token URL - referenced as <datasphere-token-url>

3.2. Creating OAuth2AuthorizationCode Destination

The authentication method supported by SAP Datasphere Consumption APIs is the three-legged OAuth2.0 flow with "Authorization Code". However, this authentication method is not supported by BTP Destinations when created via the cockpit, as detailed on this SAP Help Page. In this case, the only viable creation method is through the SAP Destination Service API, as described here. In this blog post we will use a HTTP client (eg: Postman) the request should look like:

URL: https://<destination-service-url>/destination-configuration/v1/subaccountDestinations/ 
Method: POST
Headers:

  • Authentication: Bearer <token> - Follow steps here for generating authentication token
  • Content-Type: application/json

Body:

 

 

{
  "Name": "DWC_Sales_OrdersTypeCode",
  "Type": "HTTP", 
  "URL": "<your-datasphere-url>" //Doesn't matter in this case,
  "Authentication": "OAuth2AuthorizationCode",
  "ProxyType": "Internet",
  "tokenServiceURLType": "Dedicated",
  "HTML5.DynamicDestination": "true",
  "clientId": "<client-id>",
  "clientSecret": "<client-secret>",
  "AppgyverEnabled": "true",
  "tokenServiceURL": "<datasphere-token-url>",
  "WebIDEUsage": "true"
}

 

 

Note: <destination-service-url> can be found in the binding between any BTP App and the Destination Service in the BTP cockpit (some more details on this SAP Help Page)

4. Modeling an SAP Datasphere view to be used on SAP Build

Upon completing the pre-requirements setup in sections 2 and 3, focus can then be shifted to developing the SAP Datasphere view according to individual business needs. This blog post primarily addresses the integration between SAP Datasphere and SAP Build, hence this section will cover a basic setup of a view intended to be accessed by SAP Build.

The most crucial part of creating an app involves understanding the problem that needs solved, as this will drive how data, views and analytic models should be modeled on SAP Dataspehre. In the context of this blog, for example, the SAP Build app will expose a list of countries and the sales value of each of them, also allowing to drill down into each one to check details of the specific product sales in each country.

On this blog a simple view named "CountrySales" containing 2 measures and 3 attributes. The measures will aggregate the data allowing to extract value from it in later in the SAP Build App, the image bellow shows a bit more about the modeling of the view. Also, don't forget to mark your view as "Exposed for Consumption", as required by Consumption APIs and import data into your base local or remote tables, more details can be found here.

view-modeling.png

After deploying the view it becomes accessible through the Consumption OData APIs of SAP Datasphere. The a catalog request can be used check all available assets for consumption.  

Asset Catalog request: https://<your-datasphere-url>/api/v1/dwc/catalog/assets

There are 2 ways of consuming the exposed view via relational or analytical type of consumption, in this blog post we will use the first one due to some limitations on SAP Build to handle analytical consumption type. An example of the consumption URL that will be used later on is defined bellow - it will be references as <country-sales-consumption-api>

https://<your-datasphere-host>/api/v1/dwc/consumption/<mySpace>/CountrySales/ 

For learning more about SAP Datasphere consumption APIs, checkout Mirko's blog post.

5. Configuring SAP Build Authentication

To start configuring the SAP Build App the first task to be executed is setting up the authentication and authorization using the Authorization Code flow within the App. To achieve this, we will create a page containing a webview component for an in-app browser experience. This webview will open the <datasphere-authorization-url> to start the authentication process explained on section 1. Once this flow concludes in the browser, we will add specific logic within SAP Build to capture the returned token and assign it to variables for later use.

5.1. Configuring the WebView component

  1. Create a new SAP Build Page, lets call it "Datasphere Auth Page"
  2. Set this page as the initial page of your application
  3. Add a "WebView" component to the empty page just created:
    1. Set the URL with pattern (replace parts in red) - in the example a SAP Build variable was used: 
      <datasphere-authorization-url>/response_type=code&client_id=<client-id>
      • https:/your-dwc-tenant.authentication....sap/oauth/authorize?response_type=code&client_id=sb-5a0f6ce9-3ac2-4425-9d27-1d78ce6be785!b13811%7Cclient!b2319
      • Note: In case you have a bad request error, replace the | (pipe) in the clientID by %7C
        build-webview.png
    2. As outcome of this step when you open your app it should show up the login screen of the Identity Provider (IdP) linked with your SAP Datasphere application.
      build-idp-login.png

       

After the login on the IdP is performed a blank screen should be displayed, which is the redirect page developed on section 2.3.

5.2. Handling After Login action

The concept behind the after login logic is to detect when the page displayed on the webview matches the pattern of the redirect URL used on section 2.3, in this case “/oauth/token?access_token=*” . When this happens the access token can be extracted from the URL query parameter, stored into an App variable and the page redirected to the fist content page of the App.

For the logic setup, open the "Logic Canvas" of the "Webview" component and follow the steps, at the end it should look similar to the image:

  1. Start with the event “Component onLocationChange”

  2. Include some Javascript code to detect the URL pattern and extract the access token:

 

 

if(inputs.input1.url.includes('/oauth/token?access_token')){
  var code = inputs.input1.url.split('access_token=')[1].split('&')[0];
  return { code: code, codeAvailable: true } 
}else{
  return { codeAvailable: false, debug: JSON.stringify(inputs.input1) } 
}

 

 

  • Use an "IF" to check if the access token is available

    1. In case it is available:

      1. Set it into an App variable – this will be referenced as <app-var-access-token>

      2. Change page to next one
      3. Dismiss the current authentication view

       

    2. In case not available, rise an error

build-webview-logic.png

 

6. Setup SAP Build App Data Model

The last step to make the App usable is to create pages and make request to Datasphere Consumption APIs to fetch the data and make it available on the App screen.

6.1. Creating the Data Model

For that SAP Build data entities need to be configured for each of the assets of SAP Datasphere that will be required in the App. Once the data models are configured they can be easily bound to list, tables and charts on the App. The steps bellow explain how to configure a data model:

  1. Open data tab on SAP Build
  2. Create a data entity of type "OData Integration"
    1. As Authorization chose "Bearer Token"
    2. Generate an OAuth2.0 Bearer token using the information from SAP Datasphere – this is only required for SAP Build to fetch the model metadata.
    3. Provide the Consumption URL of the SAP Datasphere view
      build-data-model.png

       

    4. Click verify URL and the metadata of the view should be available
    5. Save the data model, it will be called "CountrySales"

6.2. Using the Data Model

To use the data models created a request for the records need to be added to the life cycle of a page and the authorization token of this request should be set correctly using the <app-var-access-token> as part of the headers.

The steps bellow explain how to create a page, configure the data model and use the filter or parameter capabilities of SAP Datasphere to search for specific data:

  1. Create or use the page redirected after login
  2. Go to the variables tab section
    1. Create a Data Variable for storing the model data that will be fetched – lets call it <data-var-country-sales>
    2. Create a page variable for storing the search text field value – lets call it <page-var-search>
  3. On the page add some components:
    1. Add a “Large Image List Item” component
      1. As “Repeat With” set <data-var-country-sales> variable
      2. As “Title Label” set “current.CountryName” , so the country name will appear as title of the list item
      3. As “Description T ext” set a formula with the following content (to format the monetary information)
        • "Amount: $" + FORMAT_LOCALIZED_DECIMAL(NUMBER(repeated.current.Weighted_Value), "en", 2)
      4. As “Image Source” set the formula bellow just make the list a bit prettier (It will render country code as colored image:

        • "https://ui-avatars.com/api/?rounded=true&background=5F308E&&color=FFFFFF&size=64&name=" + ENCODE_FOR_URL(repeated.current.CountryCode)
    2. Add a Spinner component to serve as busy indicator for the request

build-countries-page.png

When the App components have been added in the place they belong on the screen. The missing part is to add behaviour and load data to each of them. First when the page loads the data from the Data Model created on the step before needs to be loaded, also some caveat needs to be added for showing a "spinner" during loading time. Follow the steps bellow for setting this.

  1. Add an Event "Page Mounted"
  2. Set an Page Variable <is_loading> to true
  3. Link it to a "Get Record Collection" data block, with settings
    1. As Resource Name: Use the "CountrySales" model created on step 6.1
    2. As: Bearer token authentication: Use a custom object with, type "Bearer" and token the variable <app-var-access-token>, created on step 5.2.
      build-request-api-auth.png
  4. Link it "Set Data Variable" block to store the request result into the <data-var-country-sales> variable
  5. Link it to a "Set an Page Variable" setting <is_loading> to false, as loading is completed

This finishes the development of a simple SAP Build app fully integrated with SAP Datasphere via its Consumption APIs, a video of an extended version of the App is available bellow.

 

To conclude, this blog post serves as an exhaustive guide for the integration of an SAP Build app with SAP Datasphere, while also laying the foundational knowledge for applying these steps to integrate other products and clients that support OAuth authentication with Authorization Code grant type. The elaborate instructions encompass crucial processes such as understanding the authentication flow, making effective use of the Consumption oData APIs, shaping the BTP Authorization Backend, setting up secure authentication, creating an OAuth Client & Destination, and establishing the SAP Build App Data Model.

 

 

 

5 Comments
Cocquerel
Active Contributor
0 Kudos

As far as I have understood, the OAuth Client in SAP Datasphere is authorized to consume all datasphere objects that are exposed for consumption without any possibility to make restriction by OAuth Client.
That looks to me a bit dangerous from security point of view.
What if we create a Datasphere data access control object to protect the access to view ? I mean, what is the user id that we get in case the view is consumed from the oData datasphere service ? Could we put in place a logic in the data access control object in order to hide all records depending on the OAuth Client ID ?

gustavokath
Product and Topic Expert
Product and Topic Expert

Hi @Cocquerel,

The objects exposed for consumption in SAP Datasphere are only available for the business users which have the required privilege to the space the object belongs to. 

An access token without a business user, generated with client_credentials type for example, does not have access to any object and can not use SAP Datasphere Consumption APIs. This is the reason why a 3-legged OAuth flow with authorization_code grant type (covered in this Blog) or SAML Bearer Assertion  is required.

On the 3-legged OAuth flow with authorization_code grant type, during the IDP Authentication process the business user must provide their credentials. If the login is successful the IDP redirects the call so the access token can be generated bounded to the user which logged-in. Therefore all API requests (using this access token) will be realized within the scope of what this user can do, this means if you have a DAC applied to an object the restrictions will be applied based on what the used can see.

Let me know if you have any questions on this process.
Best Regards
Gustavo Kath

Cocquerel
Active Contributor
0 Kudos

thanks for the explanation

 

js2
Product and Topic Expert
Product and Topic Expert
0 Kudos

How does this compare to the PKCE authorisation flow with IAS? Is one better than the other? Why would you use one or the other? 

gustavokath
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hi @js2,

Like the authorization code grant type, the PKCE (Proof Key for Code Exchange) authorization flow adds an additional layer of security, particularly useful in public clients where your key can't be securely stored. In this case we used the destination service to store the client secret and keep it on the backend side.

Whether one is better than the other largely depends on your specific use case. The authorization code flow might be sufficient for most situations where you are dealing with confidential clients, i.e., an application that can secure the client_secret, like a web server. PKCE is generally recommended for public clients such as mobile or JavaScript applications – basically any client that can't keep a secret. Also the PKCE needs to be supported by the IDP in SAP case usually IAS which needs OpenID Connect to be used.

Let me know if you have any questions
Gustavo Kath