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: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert
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 endpoint that is protected with OAuth / OIDC based on IAS.
Technologies covered:
SAP Business Technology Platform (BTP), Cloud Foundry
SAP Cloud Integration (CPI) - HTTP Receiver adapter
Identity Authentication Service (IAS)

Quicklinks:
Takeaways
Sample Code



Content


0. Prerequisites
1. Introduction
2. Configure Security Material
3. Configure IAS
4. Configure iFlow
5. Configure Target Application
Appendix: Sample Code

0. Prerequisites



  • Access to BTP and admin permissions in subaccount

  • Access to a Cloud Integration tenant.

  • Access to IAS

  • Basic knowledge about OAuth


1. Introduction


In SAP BTP Cloud Foundry environment, we’re creating an application and we’re using IAS / OIDC to protect it.
We have a scenario where the iFlow calls this external endpoint, via HTTP Adapter.
In the property sheet of the HTTP Adapter we configure the authentication for the target.
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.

When creating a Credential artifact, we have to consider that the configuration differs, depending on the service used to protect the target endpoint.
This blog posts show how to do so if the target endpoint is protected with Identity Authentication Service (IAS).

The scenario looks as follows:


The diagram shows:
We have an application running in Cloud Foundry.
It is protected with an instance of Identity Service.
The iFlow is supposed to call this application.
The HTTP Adapter is configured to use credentials of type OAuth2 Client Credentials.
Means that the OAuth-flow “client-credentials” is used to fetch a JWT token for the target app.
This is done under the hood and the obtained JWT token is sent to the target.
Very helpful and easy.

IAS supports OAuth2 and the grant type Client Credentials, as expected.
As such, it is easy to configure the Credentials.
Also, using the Authorization Code flow for authenticating at the target application is possible to configure for IAS.

We just need to quickly check how it is done.
The following chapter describes how the Credentials are created.

2. Configure Security Material


In my example, I created the target application beforehand, so I can read the environment variables of it.
This is required to view the credentials of the Identity service which is bound to my app.
The Identity service acts as proxy to the IAS and is responsible for creating an OAuth client.
Alternatively, the required OAuth-credentials can be viewed by creating a service key.
If you aren't using Cloud Foundry, then you can always create an OAuth client in the IAS cockpit, by creating a new "Application" entry.

Before proceeding with below description, we need to have these credentials at hand.
In my example:


I've marked the required properties.

2.1. Client Credentials


In this blog post we're not explaining OAuth 2.0, just a little note.
Note:
If a target resource is protected with OAuth, we cannot send a user password (like Basic Auth), but instead, we need to fetch a JWT token from the Authorization Server. This is done via an HTTP request and we need to authenticate with clientid/secret. The JWT token is a little piece of data, encoded with Base64. This is sent as "Bearer" token to the protected resource.

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: "iftonoapp_IAS_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.
In case of IAS, we get the required value in the url property of the binding information.
However, this is only the host URL.
We need to append the token endpoint.
In case of IAS, the segments to be appended are /oauth2/token
Example or full URL:
https://ias.accounts400.ondemand.com/oauth2/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 create an instance of Identity in BTP, as described above.
The ID of the client can be found in the binding information

🔸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 the binding information

🔸Client Authentication
The CPI runtime fetches a JWT token behind the scenes.
To do so, an HTTP request is fired to the Authorization Server (IAS in this case).
The server requires authentication and the clientid/secret are used for that purpose.
Depending on the server-implementation, they have to be sent like a normal Basic Auth.
Alternatively, they can be sent in the request body of a POST request, as key-value pairs.
The selection of this field has to match the choice of the content type field below.
We choose:
Send as Body Parameter

🔸Scope
The JWT token contains authorization information that may be required by the target application.
For instance, the app can required one role for reading data, and a different role for writing data.
When fetching a JWT token, a scope parameter can be specified, to avoid that too many scopes are sent.
Usually, this field, which actually is a filter, can remain empty, so that just all scopes are sent.
If we want to enter a value here, we need to make sure that the content-type is set to url-encoded

🔸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
If "send as header" was specified above and no scopes are specified, then we can choose application/json

🔸Resource
In our IAS-scenario this field is not required.
Detailed info in rfc8693

🔸Audience
In our IAS-scenario this field is not required.
Detailed info in rfc8693



2.2. Authorization Code


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

What is Authorization Code flow?
It is kind of standard way used by user-facing web applications for authorizing end-users.
Unlike client-credentials, is is user-centric, it is interactive, it requires an active login by a real user.
It is a bit more complicated because it adds another server-roundtrip, thus more safe.
This second server-roundtrip brings a prerequisite: the web-app which requires user-login has to provide a dedicated endpoint to be called by the Authorization Server.
And this prerequisite makes it difficult for CPI to offer a simple Security Material for this flow.
But we can do it.

How does the Authorization Code flow work?
A user wants to access a protected endpoint.
He has no JWT token at hand.
The first call to the Authorization server is initiated to the /authorize endpoint of it.
The server asks the user for login and - if successful - generates a Code.
But this code is not sent as response, it is sent to the dedicated endpoint of the web app.
The URL of this dedicated endpoint is mentioned by redirect-URL
It must be registered beforehand at the Authorization Server (when creating/registering an OAuth client).
If IAS doesn’t find the redirect-URL in its list of trusted targets, then the redirect is refused and JWT token not issued.
As such, the redirect cannot be redirected by hackers, hence this adds a layer of security.
The dedicated endpoint initiates the second server call to the /token endpoint of the Auth Server: it sends the code and receives a JWT token
Done.

Challenge:
At runtime, while processing an iFlow, there's no user-interaction possible.

The trick is:
The CPI runtime itself offers this dedicated endpoint.
As such, it can fetch the desired beautiful JWT token (it has ugly name, though).
During designtime, an interactive step is possible.

So 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: "iftonoapp_IAS_AuthCode".

🔸Description
Any descriptive text of our choice.
It doesn’t show up anywhere.
In my example: "Authorization Code Refresh Token Credentials for IAS-based iftono app"

🔸Provider
We choose “Generic” Provider for our IAS.

🔸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”.
In case of IAS, the URL is provided in the binding property with name authorization_endpoint.
Example:
https://ias.accounts400.ondemand.com/oauth2/authorize

🔸Token Service URL
The final URL that is called by the CPI runtime for fetching a JWT token.
In case of IAS, we get the required value in the url property of the binding information.
However, this is only the host URL.
We need to append the token endpoint.
In case of IAS, the segments to be appended are /oauth2/token
Example of full URL:
https://ias.accounts400.ondemand.com/oauth2/token

🔸Redirect URL
As explained above, this information about the redirect-URL is essential, because the IAS needs to be configured with it.
See chapter below about how to configure IAS with the CPI-redirect-URL

🔸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 create an instance of Identity in BTP, as described above.
The ID of the client can be found in the binding information

🔸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 the binding information
In case of “Authorization Code”, the end-user is asked for login, because he needs to consent that the web-app calls the protected resource on his behalf and using his roles.

🔸Send As
The CPI runtime fetches a JWT token behind the scenes.
To do so, an HTTP request is fired to the Authorization Server (IAS in this case)
The server requires authentication and the clientid/secret are used for that purpose.
Depending on the server-implementation, they have to be sent like a normal Basic Auth.
Alternatively, they can be sent in the request body of a POST request, as key-value pairs.
In case of IAS, both ways are possible, so we can leave the default or select “Header” as of our mood.
In my example, we choose "Body Parameter".

🔸User Name
We can leave this empty, as the login screen anyway asks for user name.

🔸Scope
In case of IAS, we need to enter a scope name here.
IAS requires the scope parameter to be sent to the /authorize endpoint, as described in the docu.
Supported values are: openid, email, profile, groups, offline_access
Multiple values are separated by blank.
For our example, we enter "openid.


After deploying the new Security Material, we get a disgusting red error status.
But this shouldn’t surprise us, we know:
As mentioned, 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 IAS.
Here we have to enter the credentials of a valid user in IAS.
Afterwards, behind the scenes, the CPI runtime receives the redirect call from IAS.
The CPI runtime reads the code and sends it to the token service URL which is configured in the Security Material.
It also uses the clientid and secret configured there.
In the response of IAS, 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

What is a refresh token?
After fetching a JWT token with interactive user login, the resulting JWT token can used to access the target resource,  but it has a short validity, for safety reasons.
When the target resource is requested again, the user might feel bothered to enter credentials again.
As such, the Authorization Server issues a refresh token along with the JWT token (access token).
Next time, the refresh token is used to fetch a new access token (if expired).
A refresh token remains valid during a much longer period.

Note:
Even a refresh token expires at some point in time.
In that case, the iFlow outbound call will fail, and we need to go to “Security Materials” and press the “Authorize” Button again.
That’s life.

If the iFlow outbound call fails, at least, the error message is helpful:

SecureStoreException
Problem during retrieving the access token for the OAuth2 Authorization Code credential
TokenAccessException:
Access token request via refresh_token grant type for OAuth2 Authorization Code credential failed
"error":"invalid_grant","error_description":"Refresh token is invalid or expired."

Note on validity:
Depending on the server implementation, the refresh token is renewed at each token-request.
Or not.
Or it remains valid forever.
In case of IAS, the refresh token validity is equal to the session timeout setting of IAS, as described here.
The default setting in IAS is 12 hours.
Anyways, the validity can be configured in the token-request.

And the old refresh token?
For safety reasons, it makes sense to renew the refresh token and invalidate the old one.
In case of IAS, we can configure the refresh token to be invalidated immediately.

Note:
Refresh tokens are issued only for user-centric OAuth flows like “Authorization Code” or “Resource Owner Password Credentials” flows.
Otherwise it doesn’t make sense, as no user login would be required anyways.

Note:
If you get below error when trying to “Authorize”, then the reason could be that the redirect URL is not properly maintained in the IAS


See below how to maintain the redirect-url in IAS

TipTipTip
Finally, a little tip for the configuration:
We can add a parameter to the “Authorization URL” field which will make life easier:
login_hint>
The value given here will be prefilled in the login screen of IAS
Example:
https://ias.accounts400.ondemand.com/oauth2/authorize?login_hint=carlos.roggan@sap.com

3. Configure IAS


In SAP BTP, the Identity Service takes care of communicating with IAS.
When creating an instance of identity service, the redirect-URL of the CPI runtime has to be added to the corresponding property in the config file.
In my example:

🔷config-ias.json
{
"display-name": "backendias",
"oauth2-configuration": {
"redirect-uris": [
. . .
https://subdomain.intsuite.cfapps/itspaces/odata/api/v1/OAuthTokenFromCode
]
}
}

Note that I’ve shortend the URL.

The service instance can then be created with the following command:

cf cs identity application backendIas -c config-ias.json

If the instance was already created before, then it can be updated with the new configuration.

cf update-service backendIas -c config-ias.json

Alternatively, the property can be edited in the IAS cockpit at Applications & Resources
Select your <display name> of your instance in the list.
In the details pane, select OpenID Connect Configuration and press “Add” to enter the redirect URL which is shown in the dialog of “Security Materials” in CPI.



4. Configure iFlow


For the sake of completeness, I’m showing the configurations of my test-iFlows.

4.1. Client Credentials


Below screenshot shows the configuration of the HTTP Receiver Adapter



4.2. 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.
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("iftonoapp_IAS_AuthCode");
String token = accessTokenAndUser.getAccessToken();

message.setHeader("Authorization", "Bearer "+token);

The full  content can be found in the appendix.

5. Configure Target Application


For those who could be interested in replaying my test setup, I'm sharing my dummy target application.
It just provides a protected endpoint, nothing else is required.
The app is configured with the instance of Identity service created above.

🔷manifest.yml
applications:
- name: iasbackendapp
routes:
- route: iasbackendapp.cfapps.eu12.hana.ondemand.com
services:
- name: backendIas
. . .

The server implementation uses the binding to Identity service to configure passport which is responsible of protecting the endpoint.
If the endpoint receives a valid JWT token, the token is decoded and the content is printed to the console.

Note:
Authorization (i.e. user roles) is not covered by this simple app.

🔷server.js
const IAS_CREDENTIALS = xsenv.getServices({myIas: {label:'identity'}}).myIas 
passport.use('JWT', new JWTStrategy(IAS_CREDENTIALS, "IAS"))
app.use(passport.authenticate('JWT', { session: false, failWithError: true }));

app.get('/endpoint', async (req, res) => {
const jwtDecoded = req.tokenInfo.getPayload()
console.log(`===> The full JWT decoded: ${JSON.stringify(jwtDecoded)}`)
res.send(JSON.stringify(jwtDecoded))
})

The full file content can be found in the appendix.

Summary


In the present blog post, we’ve learned how to call an IAS-protected application from an iFlow via HTTP Receiver adapter.
IAS supports OIDC which is based on OAuth, such that everything works as expected.
We only need to figure out how to configure the Security Material in CPI.

Key Takeaways


Token Endpoint: /oauth2/token
Authorize Endpoint: /oauth2/authorize
Security Material Authorization Code:
Scope is required for IAS: openid
Redirect URL has to be added to white-list in IAS

Links


SAP Help Portal

CPI:
Authorization Code Credential
Receiver Adapter
Javadoc for Groovy scripting main entry

SAP Cloud Identity Services - Identity Authentication (IAS):
Entry in Discovery Center
IAS Landing Page
IAS: getting a tenant
Establish trust
OAuth flows in IAS main page
Authorization Code flow in IAS
Refresh Token flow
Identity Service main
Identity service reference for config params

Node.js
IAS supported by Security Library for node.js

Blog Posts
Inbound scenario from IAS-based app to iFlow
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


 

config-ias.json

{
"display-name": "backendias",
"oauth2-configuration":
{
"redirect-uris":
[
"https://subdomain.integrationsuite-it.cfapps.eu12.hana.ondemand.com/itspaces/odata/api/v1/OAuthTokenFromCode"
]
}
}

manifest.yml

---
applications:
- name: iasbackendapp
path: backend
memory: 64M
routes:
- route: iasbackendapp.cfapps.eu12.hana.ondemand.com
buildpacks:
- nodejs_buildpack
services:
- name: backendIas

package.json

{
"dependencies": {
"@sap/xsenv": "latest",
"@sap/xssec": "^3.2.13",
"express": "^4.17.1",
"passport": "^0.4.0"
}
}

server.js
const xsenv = require('@sap/xsenv')
const IAS_CREDENTIALS = xsenv.getServices({myIas: {label:'identity'}}).myIas

const xssec = require('@sap/xssec')
const passport = require('passport')
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(IAS_CREDENTIALS, "IAS"))
const express = require('express')
const app = express();
app.use(passport.initialize())
app.use(passport.authenticate('JWT', { session: false, failWithError: true }));


app.listen(process.env.PORT)

app.get('/endpoint', async (req, res) => {
const jwtDecoded = req.tokenInfo.getPayload()
console.log(`===> The full JWT decoded: ${JSON.stringify(jwtDecoded)}`)
res.send(JSON.stringify(jwtDecoded))
})
8 Comments