Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
JakobFra
Participant

Introduction


This series is about taking a closer look at the different destination types of the SAP BTP Cloud Foundry environment and making them more tangible with executable code examples. In the first part, we addressed the underlying motivation and started with the easiest scenario for an easy-to-digest introduction: two SAP CAP applications deployed in the Cloud Foundry environment in the same space communicate with each other via Internet / HTTP without requiring authentication in any way. Such scenarios are of course very pleasant and thankful from a developer's point of view, however, they are only practical in the rarest of cases. Sensitive and confidential data is almost always involved, and then it must be ensured that only trusted parties can access corresponding interfaces. We will therefore take the first step in this direction in the second part of this series, which deals with destinations for what is probably the simplest type of authentication: System A (client) communicates with System B (server) via the Internet / HTTP and sends an Basic Authentication header with each request, since the server would otherwise reject the request in a friendly but firm manner.

Basic Authentication


Before we look into the actual code example, let's briefly review the theory behind Basic Authentication. Basic Authentication is a method by which a client sends username and password to a server with an HTTP request for authentication purposes. The server uses this information to verify whether or not the client is authorized to make this request based on correctly supplied authentication information. Basic Authentication is stateless, i.e. the Basic Authentication header needs to be send again with every request.

The user data is sent as an HTTP request header with the Authorization header key. The value here is a Base64 encoded string with the format username:password, together with the prefix Basic. Example:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ

Important: even if the cryptic appearance may suggest it, Base64 is only suitable for encoding, not for encryption! 😉

Basic Authentication is particularly suitable for server-side authentication for a service using a technical user. Basic authentication via an application that runs on the client side, e.g., in the web browser, should be avoided. Since this requires the unencrypted password, there is a high risk that attackers will succeed in stealing the data and thus gaining unauthorized access to the service. Since credentials are transmitted unencrypted using this method, it should be ensured in any case that communication takes place encrypted via HTTPS to prevent data loss through man-in-the-middle attacks. If a generic technical user is not sufficient for authentication, but a real personal user is required, Basic Authentication is also not the means of choice, because then the disadvantage is that the server side receives not only the username but also the password in plain text and the user has no control over how this sensitive information is further processed. In this case, a token-based protocol such as OAuth2 should be used. We will look at this in detail in later parts of this series.

Cloud 2 Cloud – Basic Authentication


https://github.com/jfranken/sap-btp-destinations/tree/cloud-2-cloud-basic-authentication

The underlying architecture of the Basic Authentication Destinations example is almost identical to the architecture of the No Authentication example. Two SAP CAP applications, client and server, are deployed in the same Cloud Foundry space. A destination to the server is stored in the destination service, the client application reads the detailed information about this destination via HTTP request to the destination service. In order to be able to execute this request successfully, an access token must first be requested via the platform UAA, and this token must then be sent to the destination service in the request. To obtain a token, the client ID and client secret of the destination service are sent to the platform UAA; basic authentication is also used here.


The complete communication flow is shown in the diagram below:


For a better illustration, the complete response of the Destination Service is also shown here. As you can see, it not only contains the destination configuration, but also a kitchen-ready AuthToken that contains the Base64-encoded username-password pair:
{
"owner": {
"SubaccountId": "00000000-0000-0000-0000-000000000000",
"InstanceId": "00000000-0000-0000-0000-000000000000"
},
"destinationConfiguration": {
"Name": "Server",
"Type": "HTTP",
"URL": "https://00000000trial-dev-server.cfapps.0000-000.hana.ondemand.com/server/helloWorldServer()",
"Authentication": "BasicAuthentication",
"ProxyType": "Internet",
"User": "myUsername",
"Password": "superStrongPassword"
},
"authTokens": [
{
"type": "Basic",
"value": "bXlVc2VybmFtZTpzdXBlclN0cm9uZ1Bhc3N3b3Jk",
"http_header": {
"key": "Authorization",
"value": "Basic bXlVc2VybmFtZTpzdXBlclN0cm9uZ1Bhc3N3b3Jk"
}
}
]
}

Client Implementation


As in the first part of the series, the client includes two alternative implementations. On the one hand, the server call via Cloud SDK, and on the other hand, a plain variant that illustrates the communication processes step by step.

Cloud SDK


srv.on('helloWorldClientCloudSDK', async () => {
return executeHttpRequest({ destinationName: 'Server' })
.then((response) => response.data)
})

Plain


srv.on('helloWorldClientPlain', async () => {
// ...
// fetching token for destination service and reading
// destination from destination service is handled
// in the same way as in part 1 of this series
// ...
const destinationResponse = await axios.get(
destination.destinationConfiguration.URL, {
headers: {
// alternatively, you can also read the already
// encoded value from destination.authTokens[0].value
Authorization: 'Basic ' +
btoa(
destination.destinationConfiguration.User +
':' +
destination.destinationConfiguration.Password
),
},
}
)
return destinationResponse.data
}

Server Implementation


The server implementation is kept relatively simple, and despite the disclaimer at the end of this chapter, I explicitly point out once again: this implementation presented here is not suitable for productive use!

When the request arrives, the server reads the authorization header, decodes username and password and checks if they match the expected values. If yes, the already known response "Hello World from Server" is returned. Otherwise, the string "Unauthorized" is used to indicate that the credentials are not correct.
module.exports = async (srv) => {
srv.on('helloWorldServer', async (req) => {
const authHeader = req.headers.authorization
if (authHeader.indexOf('Basic ') === 0) {
const username = atob(authHeader.split(' ')[1]).split(':')[0]
const password = atob(authHeader.split(' ')[1]).split(':')[1]
if (username === 'myUsername' && password === 'superStrongPassword') {
return 'Hello World from Server'
}
}
return 'Unauthorized'
})
}

Execution


Once the client and server are deployed and the destination is configured correctly, calls to both client endpoints should return the following result:


The moment has come for us to give ourselves a quick appreciative pat on the back. We have implemented a server that is secured by a technical user and that our client can access with an appropriately configured destination. This is still quite "basic", but admittedly the name of this authentication type doesn't promise more. In the next part of the series we will deal with the OAuth2 client credential flow (OAuth2ClientCredentials). While we are still on the level of technical users, the following parts will then deal with the question of how the identity of a real end user can be passed on from the client to the server, where we will distinguish between two different scenarios:

  1. Client and server are deployed within the same subaccount (OAuth2UserTokenExchange).

  2. Client and server are deployed in different subaccounts (or even different global accounts) (OAuth2SAMLBearerAssertion)


Until then, have fun trying it out and as always, I'm looking forward to your feedback! 🙂

Disclaimer


The code examples provided are not suitable to be transferred 1:1 into productive implementations, but they only serve to illustrate the functioning of destinations and Destination Service. In some cases, the interaction with the Destination Service is deliberately implemented in a “cumbersome” manner in order to illustrate the communication processes as explicitly and in detail as possible. SAP CAP and the SAP Cloud SDK provide various functionality to simplify and abstract the use of destinations. However, this would be more of a hindrance than a benefit to the purpose of the illustration.
Labels in this area