SAP Cloud Integration: How to call Azure Application from iFlow (Outbound)

SAP Cloud Integration (aka CPI) allows to call an external REST endpoint from an iFlow via HTTP (receiver adapter).
It supports authentication like OAuth, Basic Auth and Client Certificate for calling a protected endpoint.
In case of OAuth, it means that Cloud Integration is able to fetch a JWT token and send it to the receiver automatically.
This blog post describes how to configure Cloud Integration to call an application that runs in Microsoft Azure
Technologies covered:
SAP Business Technology Platform (BTP), Cloud Foundry
SAP Cloud Integration (CPI) – HTTP Receiver adapter
Microsoft Azure / Microsoft Entry ID (aka Azure Active Directory, or AAD)

Quicklinks:
Takeaways
Sample Code

Content

0. Prerequisites
1. Introduction
2. Azure
2.1. Create Web Application
2.2. Create OAuth Client in Entry ID (Active Directory)
3. CPI
3.1. Create Security Material
3.1.1. Create Client Credentials Artifact
3.1.2. Create Authorization Code Credentials Artifact
3.2. Create iFlow
4. Run
Appendix: Sample Code

0. Prerequisites

  • Access to a Cloud Integration tenant.
  • Access to Microsoft Azure and admin permission for Azure Active Directory.
    Can use free trial.
  • Access to the sibling blog post
  • Basic knowledge about OAuth

1. Introduction

In the sibling blog post, I describe a scenario where an iFlow calls an application in BTP, that is protected using IAS (instead of XSUAA).
The details and concepts are explained in detail there.
In the present blog post, we want to call an application that runs in Azure.

CPI can handle the authentication automatically.
In this blog post, we’re considering 2 authentication options:
🔸OAuth2 Client Credentials
🔸OAuth2 Authorization Code

In both cases, CPI can fetch a JWT token for us and send it to the target endpoint.
As a prerequisite, we need to create a corresponding Credentials entry in the Security Materials dashboard.

The concepts are the same, so please refer to the sibling blog post for explanations and let’s focus only on the configurations and scenario setup.

The diagram shows our scenario setup:
In our Cloud Integration tenant, we have an iFlow that is supposed to call an application that runs in Microsoft Azure.
To handle authentication based on OAuth, the HTTP Receiver adapter is configured with an OAuth Credentials Artifact.
This artifact is created in the “Security Material” section of CPI.
Such an artifact is configured with credentials of an OAuth client that is created in “App registrations” section of Microsoft Entry ID in Azure.
At runtime, when a JWT is needed, the credentials artifact fetches a JWT token automatically from Entry ID, which acts as OAuth Authorization server in Azure.
This token can then be sent to the Azure-application.

Note that there’s of any Trust Configuration in BTP.
We’re manually copying over the credentials and metadata from Azure to CPI.

In this blog post, we’re creating a very simple application in Azure which serves as target for the iFlow receiver adapter.

It just prints the incoming JWT token.

2. Azure

In this chapter, we’re going to deploy a simple web application to azure, which will serve as target for the receiver adapter in our iFlow.
To configure the OAuth credentials, we create an OAuth client in Microsoft Entry ID, in Azure.

2.1. Create Web Application

We create a very simple Node.js application in Azure.
Assuming that all readers will skip this chapter, I’ll try to be short.
Enter portal.azure.com

2.1.1. Preparation in the Azure Portal

Create a “Resource Group”, if not done already.
Then go to App Services -> Create -> Web App
In my example, I’ve given the name as “cpitoaz”.

After creation of the new Web App, it will be deployed and we can configure it at
Azure Portal Home -> App Services -> cpitoaz

To see the result in different view, without installing the Azure command line client, let’s open the cloud shell in a new browser window:
https://portal.azure.com/#cloudshell

Our new web app should be found via
az webapp list
or better
az webapp list –query [].name
and then e.g.
az webapp list –query [0].name
if our new web app is the first in the list.

2.1.2. Write the sample code
For writing our very simple sample, we can use the cloud shell.
It opens in our home directory.

First we create a project folder
mkdir cpitoaz
And step into it
cd cpitoaz

We create the 2 necessary files for our node app:
touch package.json
touch app.js

Now we can open an editor for editing the files
code .

First we open package.json with double-click and paste the following content

{
  "scripts":{
    "start": "node app.js"
  }
}

We save with Ctrl + S
Now open the app.js file and paste the following content:

const http = require('http');

const server = http.createServer(async function(request, response) {

   console.log('===> [APP]  App endpoint successfully invoked')
   const jwtEncoded = request.headers.authorization
   if(jwtEncoded){
        const jwtBase64Encoded = jwtEncoded.split('.')[1]
        const jwtDecodedAsString = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii')
        console.log('===> [APP]  the JWT: ' + jwtDecodedAsString)
   }else{
       console.log('===> [APP]  Error: App endpoint invoked without Authorization header')
   }

    response.writeHead(200, { "Content-Type": "text/html" });
    response.end(`Azure App successfully invoked. Received Authorization header: ${request.headers.authorization}`);
});

const port = process.env.PORT || 1337;
server.listen(port);
console.log(`===> Server running at http://localhost:${port}`);

The full  content can be found in the appendix.

Save with Ctrl + S
Close the editor with Ctrl + Q

2.1.3. Deploy the code

We should ensure that we’re going to deploy the code to the right app:
az webapp list –query [0].name

So now we can fetch the required information and store in variables:
export APPNAME=$(az webapp list –query [0].name –output tsv)
export APPRG=$(az webapp list –query [0].resourceGroup –output tsv)
export APPPLAN=$(az appservice plan list –query [0].name –output tsv)
export APPSKU=$(az appservice plan list –query [0].sku.name –output tsv)
export APPLOCATION=$(az appservice plan list –query [0].location –output tsv)

Finally we use these variables to run the deploy command:

az webapp up –name $APPNAME –resource-group $APPRG –plan $APPPLAN –sku $APPSKU –location “$APPLOCATION”

Alternatively, find the information in the portal WebApp overview page and compose the command manually:
Portal Home -> App Services -> cpitoaz

az webapp up –name cpitoaz –resource-group rg_webapp –plan ASP-rgwebapp-85eb –sku F1 –location ‘East US’

After successful deployment, we see an output like this:

It contains the web app URL, which we can find as well in the overview page in the portal at “Default domain”.
Note that we should take the https-variant.
In my example:
https://cpitoaz.azurewebsites.net

We copy the link to our scratchpad.

The Link in the shell is clickable, so we click it and our app opens and outputs our dummy response text.
As we’ve opened it in browser without sending a JWT token, the auth header is empty.

To view the logs, we go to
Portal Home -> App Services -> cpitoaz
In the left navigation pane we scroll down to Monitoring -> Log stream

That’s it for now.
We’ve created an application in Azure that is not protected but prints the content of the received JWT token.

2.2. Create OAuth Client in Entry ID (Active Directory)

We need to create an OAuth client in Azure, which is used to fetch a valid token and to protect the web application.
In Azure speech, such OAuth client is called “App registration”.

2.2.1. Create App registration

We go to Entry ID via Portal Home -> Microsoft Entry ID.
In the left navigation pane, we click on App registrations -> New registration.

🔸Name
We enter a name of our choice
🔸Account type
Single Tenant
🔸Redirect URI
Platform:
“web”
URI:
compose URL as indicated by docu:
Take the base URL of the CPI tenant and append the segments /itspaces/odata/api/v1/OAuthTokenFromCode
In my example:
https://sd.intsuite-it-xx.cfap.eu.hana.ond.com/itspaces/odata/api/v1/OAuthTokenFromCode
Note that in case of doubt, the URL can be copied from the creation dialog below, then adjusted here.

Finally press “Register”.

2.2.2. Configure App registration

In the details screen of the new App registration, we find the “Application (client) ID” and take a note of it to some scratchpad.
In my example: e95626f2-1298-45b1-bbb0-f9c96876387f

We click on “Endpoint” in the upper bar and take a note of the first 2 links:
Authorization endpoint (v2)
https://login.microsoftonline.com/7d9a2f86-f05c/oauth2/v2.0/authorize
and token endpoint (v2)
https://login.microsoftonline.com/7d9a2f86-f05c/oauth2/v2.0/token

The URL is composed with the tenant id which can be found in the overview page of Entry ID.

To use the OAuth client, we need credentials, which are not generated by default.
So we go to Certificates & secrets -> New client secret -> Add.
We copy the value of the new secret to our scratchpad, e.g.
Box8Q~ilUhubS3th~yV8RDAOf6cseGXNseoWvdoG

It won’t be visible after some time, but we can always create a new one.

Optionally, we can go to “Token configuration” to add more detailed information to the token which will be fetched later, for calling our azure-web-app.

Finally we go to “API permissions” and press on “Grant admin consent”.
This prevents users from having to go through the “Consent” dialog (in user-scenario)

With this setting we’ve finished our activities at Azure side.
Now let’s use both, the app and the OAuth client, from CPI.

3. CPI

Our goal is to call the Azure-application from an iFlow and to use the OAuth credentials which we created in the previous chapter.
This is the focus of the present blog post.

3.1. Create Security Material

In CPI, credentials are stored in “Security Material”, they can be simple user/password credentials, hidden from public, or they can be more sophisticated, such that they would automatically fetch a JWT token for us.
To fetch a JWT token, we choose the OAuth flows “Client Credentials” and “Authorization Code”.
We create 2 “Credential” artifacts which we will use in 2 iFlows below.

3.1.1. Create Client Credentials Artifact

Please refer to the corresponding sibling blog post section for more detailed explanations of the config.

In the Cloud Integration tenant, we go to Monitor Artifacts -> Manage Security -> Security Material
Click on Create -> OAuth2 Client Credentials

In the dialog, we enter the following settings:

🔸Name
Any name of our choice.
The same name has to be entered in the HTTP Adapter.
In my example: “Azure_ClientCreds”.
🔸Description
Any descriptive text of our choice.
It doesn’t show up anywhere.
🔸Token Service URL
The final URL that is called by the CPI runtime for fetching a JWT token.
We copied it to our scratchpad, in my example:
https://login.microsoftonline.com/7d9a2f86/oauth2/v2.0/token
🔸Client ID
The identifier of the OAuth client that is registered at the Authorization server and entitled to access the protected resource.
To get a client, we created an app registration in Entra ID, so we take the clientID from our scratchpad.
In my example: e95626f2-1298-45b1-bbb0-f9c96876387f
🔸ClientSecret
The password of the OAuth client.
With client id and secret, we can authenticate at the “Authorization Server” and ask for a JWT token.
The value is copied from our scratchpad, e.g. ox8Q~ilUhubS3th~yV8RDAOf6cseGXNseoWvd
🔸Client Authentication
Microsoft Entra ID requires that the authentication info is sent in the request body, otherwise it complains.
The selection of this field has to match the choice of the content type field below.
We choose: “Send as Body Parameter”
🔸Scope
Microsoft Entra ID server (aka AAD) requires that we send a scope parameter along with the token request.
The required format is:
<clientID>/.default
In my example:
e95626f2-1298-45b1-bbb0-f9c96876387f/.default
🔸Content Type
If we specify to send parameters in the request body, then the content type MUST be set to:
application/x-www-form-urlencoded
🔸Resource
In our scenario this field is not required. Detailed info in rfc8693In our scenario this field is not required. Detailed info in rfc8693

3.1.2. Create Authorization Code Credentials Artifact

In this section we’re going through the process of creating usable Security Material based on the OAuth flow Authorization Code.

Please refer to this section of the sibling blog post For more detailed explanations of the config.

Let’s go through the configuration:

🔸Name
Any name of our choice.
The same name has to be entered in the HTTP Adapter.
In my example: “Azure_AuthCode”.
🔸Description
Any descriptive text of our choice.
It doesn’t show up anywhere.
🔸Provider
We choose “Microsoft 365”.
🔸Authorization URL
 The URL of the /authorize endpoint of the OAuth Authorization Server (IAS).
This is the first URL which is called during the OAuth-flow.
It redirects to the specified “redirect-URL” and sends the authorization “code”.
We have the URL in our scratchpad, in my example:
https://login.microsoftonline.com/7d9a2f86/oauth2/v2.0/ authorize
🔸Token Service URL
The URL that is called by the CPI runtime for fetching a JWT token.
We have the URL in our scratchpad, in my example:
https://login.microsoftonline.com/7d9a2f86/oauth2/v2.0/token
🔸Redirect URL
This information about the redirect-URL is essential, because the App registration in Azure  needs to be configured with it.
In section 2.2.1. we composed the URL according to the description of docu.
We can now copy the URL from this dialog and verify if the redirect URL which we entered in Azure was correct.
In Azure, we go to Home -> Entra ID -> App registrations -> CpiToAzAppReg -> Authentication -> Platform configurations -> Web
There we can find the Redirect URL
In my example:
https://subdomain.integrationsuite-it-xx.cfapps.eu12.hana.ondemand.com/itspaces/odata/api/v1/OAuthTokenFromCode
🔸Client ID
The identifier of the OAuth client that is registered at the Authorization server and entitled to access the protected resource.
To get a client, we created an app registration in Entra ID, so we take the clientID from our scratchpad.
In my example: e95626f2-1298-45b1-bbb0-f9c96876387f
🔸ClientSecret
The password of the OAuth client.
With client id and secret, we can authenticate at the “Authorization Server” and ask for a JWT token.
The value is copied from our scratchpad, e.g. ox8Q~ilUhubS3th~yV8RDAOf6cseGXNseoWvd
🔸Send As
We choose “Body Parameter”.
🔸User Name
The user who is entitled to access the application in Azure.
In my example, I created a test user like
[email protected]
🔸Scope
Microsoft Entra ID server (aka AAD) requires that we send a scope parameter along with the token request.
The required format is:
<clientID>/.default
In my example:
e95626f2-1298-45b1-bbb0-f9c96876387f/.default

After deploying the new Security Material, we get a disgusting red error status.
But this shouldn’t surprise us, we know:
This OAuth-flow is meant to be interactive, a user has to enter his credentials, otherwise no code can be generated and sent to the redirect-endpoint.
So we need to somehow manually do the login.
CPI offers this interactive login via the context-sensitive button:

Afterwards, we’re presented the login screen sent by Microsoft, with the specified user, asking for password.
Afterwards, behind the scenes, the CPI runtime receives the redirect call from Entra ID.
The CPI runtime reads the code and sends it to the token service URL which is configured in the Credentials artifact.
It also uses the clientid and secret configured there.
In the response of Entra ID, the CPI runtime receives a JWT token along with a refresh token which is valid for some time.
This refresh token is used by the iFlow when calling the IAS-protected target application.

Note:
After doing successful login, the red “Unauthorized” is not refreshed automatically. We have to refresh the UI by pressing the refresh button of the “Security-Material” list

Note:
If you get an error when trying to “Authorize”, about incorrect configuration, then the reason could be that the redirect URL is not properly maintained in Entra ID.
In that case check the configuration in Azure at
Home -> Entra ID -> App registrations -> CpiToAzAppReg -> Authentication -> Platform configurations -> Web

3.2. Create iFlow

Let’s briefly show the 2 iFlows to view how the “Security Materials” are used.

3.2.1. Using Client Credentials

We create a very simple iFlow that uses an “HTTP Receiver Adapter” to call the Azure-Application:

The HTTP Receiver adapter is configured with the URL of the Azure-App which we have on our scratchpad.
In my example:

🔸Address
http://cpitoaz.azurewebsites.net
🔸Authentication
Client Credentials
🔸Credential Name
“Azure_ClientCreds”

3.2.1. Using Authorization Code

In order to use the “Authorization Code” credential, we have to use a different approach, because the HTTP Adapter doesn’t support this credential type in the “Authentication” drop-down.
However, we can call the credential programmatically in a groovy script.
To do so, we create a very simple iFlow with a groovy script and a HTTP Receiver adapter:

The receiver adapter is configured to do “no” authentication, because we’ll do the same in the groovy script.
An important setting, however, has to be added:
Allow to send the “Authorization” header:

The groovy script uses the API provided by com.sap.it.api.securestore.SecureStoreService to read our credential artifact and obtain the JWT token from there.
Note that the exact name of the “Security Material” artifact has to be entered in the code.
Then we set the JWT token as “Authorization” header.
With this header set for our iFlow, the target application can be successfully called

🔷Groovy Script

SecureStoreService secureStoreService = ITApiFactory.getService(SecureStoreService.class, null);
AccessTokenAndUser accessTokenAndUser = secureStoreService.getAccesTokenForOauth2AuthorizationCodeCredential("Azure_AuthCode");
String token = accessTokenAndUser.getAccessToken();
        
message.setHeader("Authorization", "Bearer "+token);

The full  content can be found in the appendix.

4. Run

To test the scenario, first of all, we activate the Log stream in Azure.
We go to Azure Portal Home -> App Services -> cpitoaz
Scroll down to “Monitoring” in the left navigation pane and click on “Log stream”.

Now we can run the 2 scenarios, using the client credentials flow and the Authorization Code flow.

4.1. Run Client Credentials scenario

We deploy the first iFlow and watch the log output that is being written by our Azure web app.
We can see in the log output that the JWT token was issued by the Entry ID server running on our current tenant.
And we can see that the token was issued for our client id which we recognize in the aud and appid claims:

Next we deploy the second iFlow.
Now we can see in the output the user information of the user which we used to “authorize” the security artifact.

Summary

In the present blog post, we’ve learned how to configure iFlow and Security Material to call an application that runs in Azure.
Microsoft Entra ID supports standard OAuth flows, only a few configuration details need to be taken care of.

Key Takeaways

Following info is required:

🔸Azure: Redirect URI
Copy from Security-Material dialog in CPI, or compose as follows:
<CPI-tenant>/itspaces/odata/api/v1/OAuthTokenFromCode
🔸CPI: Security Material
Provider: “Microsoft 365”
scope: “<clientID>/.default”
Send as: “Body Parameters”
Content Type: urlencoded

Links

SAP Help Portal

CPI:
Authorization Code Credential
Auth Code with Microsoft 365
Receiver Adapter
Javadoc for Groovy scripting main entry

Blog Posts
IAS-based app for Inbound and Outbound scenarios.
Microsoft Entry ID for Basic Authentication Inbound scenario.
Security Glossary Blog

Other
OAuth 2.0 specification at rfc6749
JWT specification: rfc7519
IANA JWT Claims.

Appendix: Sample Code

Groovy Script

import com.sap.gateway.ip.core.customdev.util.Message;
import com.sap.it.api.securestore.SecureStoreService;
import com.sap.it.api.securestore.AccessTokenAndUser;
import com.sap.it.api.ITApiFactory;


def Message processData(Message message) {

    SecureStoreService secureStoreService = ITApiFactory.getService(SecureStoreService.class, null);
    AccessTokenAndUser accessTokenAndUser = secureStoreService.getAccesTokenForOauth2AuthorizationCodeCredential("iftonoapp_IAS_AuthCode");
    String token = accessTokenAndUser.getAccessToken();
        
    message.setHeader("Authorization", "Bearer "+token);
    message.setBody("Dummy message body from groovy");
    
    return message;
}

Target Application

app.js

const http = require('http');

const server = http.createServer(async function(request, response) {

   console.log('===> [APP]  App endpoint successfully invoked')
   const jwtEncoded = request.headers.authorization
   if(jwtEncoded){
        const jwtBase64Encoded = jwtEncoded.split('.')[1]
        const jwtDecodedAsString = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii')
        console.log('===> [APP]  the JWT: ' + jwtDecodedAsString)
   }else{
       console.log('===> [APP]  Error: App endpoint invoked without Authorization header')
   }

    response.writeHead(200, { "Content-Type": "text/html" });
    response.end(`Azure App successfully invoked. Received Authorization header: ${request.headers.authorization}`);
});

const port = process.env.PORT || 1337;
server.listen(port);
console.log(`===> Server running at http://localhost:${port}`);

package.json

{
  "scripts":{
    "start": "node app.js"
  }
}
Scroll to Top