Technical Articles
SAP Cloud Platform Backend service: Tutorial [30]: API: called: from: HTML
or:
How to call
an OAuth protected Service
from HTML page
using App Router
Quicklinks:
Diagram
App Router Config
Sample Project Files
Contest
Hello dear friends of SAP Cloud Platform Backend service.
Hello
You must have wondered why we aren’t using our APIs like they’re meant to be used: from a user interface.
You didn’t hear me complaining
So now the time has come to learn how to call a Backend service API from a user interface
As usual, we start with the most minimalist example
Sounds interesting
It is an html page
Cool, with advanced controls?
No, just text
You call that user interface?
Ok, we can use formatted text.
Sigh
Let’s start with the tutorial
Sorry, why do we need a tutorial for a web page calling a REST service?
The problem is: the REST service is protected with OAuth
Means it requires the user to do the OAuth flow and fetch a JWT token
Ouuuwwww, the mystic OAuth…
Yes
Where can I learn about it?
Oh, there are great blogs here and here and here
Thank you for the question
You owe me something
Pssst
So how to solve the problem?
With App Router
What’s that?
You can find a nice tutorial here and here
You owe me
psssst
And what is Backend service?
Oh yes, thanks, I forgot the default phrase:
This tutorial is part of the series of tutorials explaining the usage of
SAP Cloud Platform Backend service in detail.
Yes, I owe you…
Can we start?
sigh
Goal
In this tutorial we learn how to call an OAuth protected OData service (Backend service API) from an HTML page in Cloud Foundry.
We learn how to overcome the problem of OAuth 2 authentication.
Easily, without own coding
It can be achieved quite easily with an App Router
As such, the mail goal of this tutorial is to learn how to configure the App Router
And how to use it
In this tutorial we’ll go through the required configuration files and explain the settings in detail.
If you get lost, it might be helpful to have a look at the flow diagram which depicts the connection of all involved files
Our goal: this nice app which should be able to call Backend service API:
Experienced users jump to the Appendix with sample project file content
The next blog will show how to achieve the same with a UI5 applicaton
Prerequisites
You need a running Backend service API.
If you don’t have yet, you can follow this blog
Later you can go through the series of tutorials
You should have some understanding of App Router.
I’ve explained it in detail and with easy examples: part 1, part 2 and part 3
You should be able to deploy an app to Cloud Foundry:
That can be done with the cockpit (see here) or using the command line client (see here), which is the recommended way
Node.js:
If you use the cockpit web UI for uploading the app as a zip file, then the archive needs to contain the whole content. In that case you need to have npm (node.js) on your machine (see here)
If you use the command line client for deployment, then you don’t need that
XSUAA:
We need an instance of XSUAA service and it must contain the scope required by Backend service.
See here
Please refer to Appendix for a copy&paste ready command to create a properly configured instance
In below example we use the instance-name “XsuaaForHtml”
Create Folder structure
We call our overall application “html_to_bs”, because we want to go from an HTML page to a Backend service API in a comfortable way
We need a project folder called “html_to_bs” which contains the manifest describing our application
It contains an appfolder for the whole application which will be deployed
The appfolder contains the configuration files for App Router
Furthermore, it contains the webapp folder which contains our UI app
In our case, the webapp folder contains just an index.html file which represents our UI app.
Go ahead and create this folder structure
C:\html_to_bs
appfolder
webapp
index.html
package.json
xs-app.json
manifest.yml
On my file system, it looks like this:
The subsequent sections will go through every configuration file and discuss the relevant settings in detail
The manifest
Let’s start with manifest.yml file, which describes our main application, which will run in SAP Cloud Platform, Cloud Foundry Environment
In the manifest we define an app name as “html_to_bs”, which will be also used as prefix for the app URL after deployment
We define a binding to an instance of XSUAA:
services:
- XsuaaForHtml
This is required, because our App Router will connect to XSUAA in order to handle the OAuth flow
Note:
as mentioned:
the instance must contain a foreign-scope-reference to the suitable scope defined by Backend service.
Otherwise you cannot get data from the Backend service API
Relevant for our scenario:
In the manifest, we define an environment variable called “destinations”.
It contains our destination, required by App Router.
In our example, the destination points to the URL of an OData service created with SAP Cloud Platform Backend service.
As mentioned, this service is protected with OAuth, so we set the parameter “forwardAuthToken” to true
This is important, otherwise the call of the service will fail.
Note:
The name of the destination has to be exactly the same like used later in App Router configuration file. In our example we call it “env_destination_bs_api”
See Appendix for the full file content
The App Router
The App Router is a component which is provided by SAP, we don’t need to write anything ourselves.
Technically, it comes as a node.js module which can be downloaded from npm registry
As such, we only need to configure the dependency in the package.json file
package.json
As usual, in our example, we only specify the minimum of settings.
In case of package.json, we have the “dependency” and the “start script”
(to update dependency version: npm install –save)
"dependencies": {
"@sap/approuter": "^6.0.1"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
See Appendix for the full file content
After creating the package.json file, you have the choice:
A) If you don’t use the command line client for deployment (see here), you have to download the approuter, such that you can deploy it with your zip file
in command shell, go to the folder appfolder and execute npm install
B) If you use command line client, you don’t need to download the approuter module, as that will be done by Cloud Foundry. Just push the app after creating all the necessary files
xs-app.json
This section describes the most prominent file of this blog: The App Router configuration file
Here we define 2 essential settings:
-> The routes
-> The authentication
As we know, the App Router is a component which is used as main entry point for a web application. It serves static content and can authenticate users.
See definition in SAP Help Portal
In our web application (which is just an HTML page) we do exactly that:
We use the App Router to serve the static content: it is the HTML page, our index.html file in the webapp folder
As such, we specify the following important settings in our xs-app.json file:
WelcomeFile:
"welcomeFile": "approuterhome/index.html"
This means:
when the root URL of our application in Cloud Foundry is opened, then the App Router will automatically open the index.html file
If we look closely at the path of the index.html file, we see: this path doesn’t exist on the file system: approuterhome/index.html
Reason: That’s not a path. It is a route
As such, we have to define this route
First route:
"source": "^/approuterhome/(.*)$"
Here we define:
Whenever a URL like <fullAppURL>/approuterhome/index.html is invoked, it will be redirected.
In our case, we want to redirect it to the real index.html file which is the static content of our app, located in folder webapp
As such, we have to define the local folder as destination or this first route:
First destination:
"localDir": "webapp"
In case of static content, a destination is not called “destination”, instead it is called “localDir”
And as expected, we define our local folder, “webapp”
First authentication:
"authenticationType": "xsuaa"
This is a relevant setting:
We declare that this route requires authentication. And the type of authentication is not just user-credentials (basic), instead we require xsuaa (due to Backend service).
That means that the App Router will do the OAuth flow for us.
Means it will display a login-screen where the end-user enters his credentials.
Note:
Even though in our html page we don’t require authentication, we specify the setting here, to simulate a professional web app flow
Because usually, a web app would require login, not only to authenticate the user, but also to check the roles which he has (authorization)
That’s it for the first route.
The user has reached our HTML page
The page displays a hyperlink, to fetch the data from the OAuth protected Backend service API.
As we know, that API is a OData service
As we know, OData services support REST
As such, the browser can display the response of the HTTP GET call, which is just some amount of structured text (xml or json)
The only problem:
The browser cannot authenticate an OAuth protected service call
As such, our hyperlink cannot simply point to the OData service
It needs the App Router, to handle the authentication.
What we’ve already explained above, is relevant here as well:
App Router to handle OAuth flow
Bottom-line:
The hyperlink should forward to a route specified in App Router configuration
Second route:
"source": "^/service/(.*)$"
So here we define a route which will be used by our web application, the HTML page.
We can specify any segment name of our choice. We only need to make sure to use it in the HTML
In our example. we call the segment “service”, to illustrate that it represents the OData service which we want to call.
Second destination:
"destination": "env_destination_bs_api"
The incoming request is routed to a URL, but the URL is externalized into a “destination” object.
The value of the “destination” setting (“env_destination_bs_api”) must exactly match the name of an existing destination.
Note:
In our case, we’ve defined the “existing destination” in the manifest file above, such that it will be available as environment variable. But a real destination configuration can be used as well (in that case, binding to destination service is required. See here)
Furthermore, it is important that our destination configuration contains the property forwardAuthToken: true, such that the token will be sent as Authorization header to our Backend service API
Second authentication:
"authenticationType": "xsuaa"
As we know that our Backend service API is protected by OAuth 2, we have to specify the authenticationType as xsuaa for our second route
OK.
That’s it for the App Router configuration.
See Appendix for the full file content
Sorry, one important question is still open
You can ask questions at https://answers.sap.com
Nice try
OK, go ahead
Before opening our web application, the user is logged in and a JWT token is provided by XSUAA.
Yes
Later, when the web application calls the OAuth protected OData service, the JWT token is required.
I know
We don’t do anything to store it and to forward it.
Ts Ts Ts
So How does it work???
The answer is: session
The App Router keeps the token in a session.
Nice feature
If required, the token is refreshed by App Router.
Very helpful
The documentation can be found here
Note:
At the end of this tutorial you’ll find an interesting exercise about session.
Don’t miss it…!
What do I get?
There might be interesting prizes
Little recap:
-> To open our web app:
We specify a route which sends the homepage-request to the local index.html file
-> To navigate to Backend service API via link:
We specify a second route which redirects to the OAuth protected OData service
-> To overcome the OAuth protection:
The App Router is configured with authentication type as xsuaa
Furthermore, the JWT token is kept in a session
-> Prerequisite:
App Router has to be bound to an instance of XSUAA
The web application
What we call web application is nothing that an index.html file, located in the webapp folder
I’m dreaming of doing this tutorial with real web app…
Fine, so please watch out for the next tutorial
The functionality of our app:
Allow the user to execute a command to fetch data from backend.
The command is a hyperlink:
<a href="/service/Products">Call OData service</a>
The reference target is the route that we specified in xs-app.json file
That’s all.
See Appendix for the full file content
The good thing about this small sample web application:
It clearly demonstrates that we don’t have to write any code, in order to call the OAuth protected OData service
If you’re interested:
Refer to this tutorial (and this) to see what needs to be coded in order to programmatically handle the OAuth flow
Thanks, not interested
sigh…
Deploy
Finally, we’re ready to deploy our app.
What is our app? I’ve read about two…
Correct, somehow, our app contains actually two apps:
-> The approuter, which doesn’t contain any code, but it is the main app which is started by Cloud Foundry.
See the “start” command in package.json, and also see the main URL which we open below
-> Furthermore, we deploy our user interface web app. It is the static content in the webapp folder. It is started by App Router, because it is configured as “welcomeFile”
If you need explanation about deployment, you may refer to this description and this
Test
After deployment, open the root URL of the application, as shown in Cloud cockpit or command line:
https://html-to-bs.cfapps.eu10.hana.ondemand.com
What happens?
The App Router is started and it wants to redirect to the “welcomeFile”, but therefore, the first route is needed and therefore, the login screen is required.
So we see the login screen and have to enter our credentials
The screenshot shows that the login screen is sent by XSUAA
Afterwards, we’re redirected to the index.html file, via the first route.
We can see it in the URL of the browser:
https://html-to-bs.cfapps.eu10.hana.ondemand.com/approuterhome/index.html
Our web page with hyperlink is displayed:
What now?
Obviously: click the link
What happens?
The second route is used to redirect the request to the Backend service API (see screenshot)
The App Router uses the JWT token which is kept in the session and sends it to Backend service
The OData service accepts the token and responds with the requested data that is displayed in the browser
Note:
To make things more clear, let’s compare the URLs:
The actual URL of the OData service, which is finally called:
https://backend-service-api…/odatav2/DEFAULT/PRODUCTSERVICE;v=1/Products
The URL which we see in the browser, using the App Router:
https://html-to-bs.cfapps.eu10.hana.ondemand.com/service/Products
OK.
We’ve reached the end of our tutorial
You promised a prize..
Oh, sorry I forgot.
Exercise:
Find out what happens if the JWT token expires before the call to the OAuth protected service is executed. Do you get an error message?
Configure your app to force timeout after 3 seconds.
I have no clue…
Have you tried?
It is too difficult…
OK, I have a secret for you
Oh, great, I love secrets
The solution is hidden in an appendix
Which appendix…?
You have to find out…
sigh
OK.
We’ve reached the end of our tutorial
We would need a do/logout route now…
Good idea, stay tuned for the next tutorial
How to reach it?
Use this route:
https://blogs.sap.com/2019/02/19/sap-cloud-platform-backend-service-overview-of-blogs/
Do I need App Router?
Come on – just click it
Summary
What have we learned in this tutorial?
The message:
Yes, OAuth protected OData service can be accessed from HTML page |
The solution:
Deploy App Router along with the web app
App Router requires no coding
Configure 2 routes:
First route to open web app
Second route to call the OAuth proteced OData service
Define destination pointing to the OData service
Configure App Router to handle OAuth flow
Next step: Proceed with this blog to write your UI5 app on top of Backend service
Links
App Router documentation in SAP Help Portal
User interface documentation: https://www.w3schools.com/tags/att_a_href.asp
This must be a joke…..?
Ads
Good moment for some advertisements…
Series of tutorials, lot of info around Backend service
Getting started with App Router: part 1, part 2, part 3
See how App Router forwards a token
Understanding OAuth part 1 and part 2
Manually accessing OAuth protected service
Implementing OAuth flow in node.js application part 1 and part 2
Install node.js and configure SAP registry
Use command line client to deploy to Cloud Foundry
Use Cloud Platform Cockpit to deploy to Cloud Foundry
Create API (OData service) in Backend service (EASY!!)
Create XSUAA instance with correct parameters for Backend service
Learn about JWT token and how to view the content
Appendix 1: Diagram
This diagram intends to show how the configuration files interact with each other.
At the same time representing the flow.
Click to enlarge
Appendix 2: test the session timeout
Solution:
In the SAP Help Portal you can find this docu page
There you learn that the session timeout can be controlled via environment variable.
Name is: SESSION_TIMEOUT
It can be entered in the manifest (see below)
The behavior of our web app is as expected:
Open the app, enter credentials
On HTML page, don’t click anything.
Drink coffee.
After some time, go back to the browser and click the hyperlink
Result:
The login screen is displayed
Since the route to Backend service requires authentication with XSUAA, the App Router takes care and displays the login screen whenever it is required
After entering valid credentials, the response of the OData service is displayed
applications:
- name: html_to_bs
. . .
env:
SESSION_TIMEOUT: 3
And where is my prize?
The prize is: you deserve the respect of the community
No prize…I knew it
Appendix 3: XSUAA
If you use command line client, you can just copy below command and execute it on the command shell. It will create the XSUAA-instance with the required scope.
Also with instance name used in this example
cf create-service xsuaa application XsuaaForHtml -c “{\”xsappname\”:\”XsuaaForHtml\”,\”tenant-mode\”:\”dedicated\”,\”foreign-scope-references\”:[\”Backend-service!t6131.AllAccess\”]}”
Appendix 4: Sample Project Files
Please find below all files you need to follow this tutorial.
Refer to Preparation section above to see the folder structure, where these files have to be located.
CDS
This CDS file was used as target OData service for this sample project.
You may use it to create an API (OData service) in SAP Cloud Platform Backend service
service ProductService {
entity Products{
key productID : String;
name : String;
description : String;
}
}
manifest.yml
---
applications:
- name: html_to_bs
memory: 256M
path: appfolder
services:
- XsuaaForHtml
env:
destinations: >
[
{
"name": "env_destination_bs_api",
"url": "https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/PRODUCTSERVICE;v=1/",
"forwardAuthToken": true
}
]
package.json
{
"name": "app1",
"dependencies": {
"@sap/approuter": "^6.0.1"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
xs-app.json
{
"welcomeFile": "approuterhome/index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/approuterhome/(.*)$",
"target": "$1",
"localDir": "webapp",
"authenticationType": "xsuaa"
},
{
"source": "^/service/(.*)$",
"target": "$1",
"destination": "env_destination_bs_api",
"authenticationType": "xsuaa"
}
]
}
index.html
<!DOCTYPE HTML>
<html>
<body >
<a href="/service/Products">Call OData service</a>
</body>
</html>
Folder structure