Technical Articles
SAP Cloud Platform Backend service: Tutorial [13]: API: called: from: external: tool
This blog is dedicated to Jan, because he asked for it
Hey Jan Penninkhof , hope you like it đ
This blog is part of a series of tutorials explaining the usage of SAP Cloud Platform Backend service in detail.
Today: How to call APIs from external application like REST client
Quicklinks:
Configure Security
Obtain Token
Call API
Use Tool
Reference
Some things in life are so natural for us that we do it without even realizing it.
For example: breathing. Talk louder when weâre nervous. Use a REST client to call an API. Smile when weâre happy.
And so on.
Similarly, we take for granted that, after creating an API with Backend service, we go to our favorite REST client to use the API.
– Nope –
Doesnât work:
<error>unauthorized</error>
An Authentication object was not found in the SecurityContext
brrrrrrrrr
But no need to be hopeless: thereâs a series of tutorialsâŚ.
Just follow this tutorial and youâll be able to do what nature wants you to do.
And donât worry: you donât need to be a security expert for following this description.
And donât worry: after following it, you wonât become a security expert.
If you’re interested, please read the OAuth blog as well
Background
At the current point in time, SAP Cloud Platform Backend service supports only a very limited set of authorization mechanisms:
- OAuth 2 authentication flow
- nothing else
Reason is the targeted usage by frontend applications rather than human testers.
However, there are reasons why we need to call the APIs also from local REST client: for example, because the cockpit is not powerful enough. This is the case whenever we need to send headers along with a request (e.g. for etag), or if we want to execute batch requests, or if we need to use PUT instead of PATCH
After going through this tutorial you’ll be able to easily test all of your APIs with a REST client.
You’ll learn that you cannot call your APIs simply providing your user credentials (Basic Authentication isn’t supported). Instead, you need to send an access token. This needs to be fetched once and can be used for some time.
These are the steps we need to do:
Overview of steps:
- Obtain authorization token
OK… I have to admit, this is a too rough overview.
Detailed overview of steps:
- Configure security in Cloud account
1.1. Create xsuaa instance
1.2. Create service key
1. Configure security in Cloud account
We need a couple of preparation steps, to be done in our cloud account, before we can request an authorization token: For that purpose there’s the service offering “Authorization & Trust Management” in the SAP Cloud Platform.
Please follow this blog to create an service instance with specific parameters for Backend service in customer or trial account:
https://blogs.sap.com/2019/04/29/sap-cloud-platform-backend-service-tutorial-0.5-configure-xsuaa/
Before continuing with the next steps, make sure to view the service key which contains some information required below.
To view the service key:
Go to your BETA-enabled subaccount -> click your space -> Services -> Service Instances
Click your instance -> Service Keys -> select your service key
We take a note of 3 relevant property values (I’ve marked them in the screenshot above)
Note:
If you don’t see some properties, you might need to maximize your browser window, or use a different browser
In my example (slightly modified):
clientid | sb-baas_access!t55555 |
clientsecret | 2abcdefghijklmnopqrstuvwxy= |
url | https://subaccount.authentication.eu10.hana.ondemand.com |
2. Obtain authorization token
Until now, everything was a one-time configuration in the cloud account.
Now we’re going to do the actual work:
Call the authorization endpoint to obtain that urgently desired token.
Without that token, we cannot call our API
Note:
This blog is all about that silly token, an endless chain of characters which don’t make any sense.
But they have a great value…
The URL
To obtain the token, we have to do a service call to the authentication server.
The url to be called is taken from the <url> – property mentioned in the previous section.
It is formed as follows:
https://<yourSubAccount>.authentication.<cloud>
The OAuth endpoint
At the end of above URL, we need to append the endpoint for oauth:
/oauth/token
Putting it together, in my example the endpoint URL looks like this:
https://subaccount.authentication.eu10.hana.ondemand.com/oauth/token
The grant type
Apart from the URL and the OAuth endpoint, thereâs some more information required: the “grant type”.
We can pass it as query parameters along with the URL
These are the required parameters:
grant_type=password
&username=<yourCloudUserEmail>
&password=<yourCloudPwd>
So, putting all together, the final URL which we call to get the token:
https://...authentication.../oauth/token?grant_type=password&username=your@gmail&password=123
In my example (slightly obfuscated)
https://betasubaccount.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=password&username=myuser@gmail.com&password=my123
Note:
Do I need to note this?
You know anyways very well that all this blog is for testing our APIs with REST client, to learn to build APIs with SAP Cloud Platform Backend service, to learn how to create and use some features (like etag, batch, etc) which cannot be tested with the cockpit.
As such, we can afford to send sensitive user credentials over the net.
In professional environment, you know that you would never send a password as URL parameter, as it would be easy to read it in HTTP logs.
BTW, using the tool support in Postman allows to at least hide the password in the UI. See appendix
Call the endpoint
To ask for the token, open a browser and pastenthat URL and press ENTER andâŚ
– Nope –
You get a popup:
brrrrrrrrr
That endpoint requires authorization.
What now?
brrrrrrrrr
Yes, I know why you complain and yes, to get the authorization-token, we need another authorization.
Donât panic, everything will be good.
We do have the necessary credentials: it is in the properties noted above
It is a simple Basic Authentication, where
“user” is the value of the property âclientidâ and
“password” is the value of the property âclientsecretâ
In my example, the (slightly obfuscated) credentials:
The response in the browser body contains a property âaccess_tokenâ â and this exactly is what we need.
The value is a long long string with lots of beautiful characters and digits.
Donât try to remember it or to type it.
Just copy and paste the value without quotation marks
Thatâs it: now we have the authorization-token.
Finally.
After a few notes, we can start using the token
Note:
You can repeat calling the OAuth endpoint – youâll always get the same token.
Always?
No, not always. It loses its validity.
You can see it in the property âexpires_in”
In my example:
“expires_in”: 43199
It is measured in seconds.
In my example:
43199 would mean 720 minutes, or 12 hours, that’s 1 full day, means 2 working days
After expiration, repeat the request to the OAuth endpoint to get a fresh new token.
Note:
I assume your memory has an expiration date, like mine.
So, if you need to refresh your memory, don’t hesitate to revisit my blog
You can go directly to the appendix as a reference
Note:
To get the authorization-token, you can leverage your Postman REST client, it has built-in tool support which makes life easier.
See appendix
Note:
Remember: One of those unavoidable reactions is to smile when happy
I bet youâre smiling, after receiving this token â although it is ugly…
I can even hear you smilingâŚ
Call API
Finally, we can call our lovely API (with REST client).
However, even this step needs a little explanation.
Actually we need 2 (two) explanations:
1. We need to find out the URL (yes, really)
2. We need to know how to specify the authorization
In detail:
1. The API URL
I bet you’re wondering, why we need any explanation at all about the URL of the API, as it is there in the cockpit of Backend service.
Yes, yes, the URL is of course taken from your API in Backend service….
However…
thereâs really one thing you need to know:
The final URL It is different.
Slightly different.
Your API-URL is shown in the cockpit as follows:
https://subacc-backend-service.cfapps.xx.hana.on…d.com/odatav2/DEFAULT/SRV
However, for external call, the URL has different prefix:
https://backend-service-api.cfapps.xx.hana.on…d.com/odatav2/DEFAULT/SRV
So, instead of
. . .<yourSubAccount>-backend-service. . .
You have to use
. . .backend-service-api. . .
In my example, the final URL to be entered in REST client:
https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav2/DEFAULT/SRV/Products
2. How to set the authorization
In your REST client, go to “Authorization” section
Specify âBearer Tokenâ as authorization type
Copy the access token and paste it into the âTokenâ field
Note:
In case your REST client doesnât support “Bearer Token” as authorization type:
You can enter the corresponding raw header.
The header entry would look like this (replace aaabbbccc ⌠with your token):
Authorization: Bearer aaabbbcccâŚ
In my example:
So, finally, after composing the URL and setting the authorization as described, you should get a success response after executing the request.
BTW:
All the procedure described in this blog can be used for any supported HTTP verb, like GET, POST, PATCH, DELETE
Note:
Once we have the token, we can call all different APIs of Backend service with it.
However, this statement holds only true as long as the API doesn’t require special role
That’s it, now we’re finally enabled to use our APIs in a more flexible way, calling them with a REST client tool.
Overview of the OAuth 2.0 flow:
Troubleshooting
What to do if you happily deploy everything and try testing and get….. Forbidden ???
See this blog for a Troubleshooting guide
Summary
In this tutorial weâve learned how to call an API from an external application, e.g. REST client tool.
This is necessary, e.g. whenever we need to add custom headers (e.g. Etag)
There’s a prerequisite: create an instance of “xsuaa” service with service key.
3 properties of the service key are relevant for obtaining an access token
The procedure of externally calling an API requires 2 requests:
1. Call OAuth endpoint to get the access token in the response
2. Call API (with token and modified prefix)
Advertisements
Don’t miss the following appendix. It explains how to use the Postman REST client which has built-in tool support for OAuth 2.0 authorization
Once you have your scenario running, you might be interesting in learning a bit more about OAuth, please visit this blog
In this blog we’ve discussed the “Password Credentials” grant type. I’d like to recommend also to have a look at this blog which explains the “Authorization Code” grand type.
In this blog we’re using external REST client tool, manually executing service requests.
If you’d like to learn to do that programmatically with node.js, checkout this blog (and little series. Really interesting, you learn about cloud concepts as well, e.g. service binding and programmatically access cloud microservices)
Appendix 1: Using Postman’s OAuth 2.0 support
From all REST clients which Iâve tried, Postman is the only one which provides enough tool support to ease the OAuth authentication for our scenario.
Please follow below description, it makes your life easier.
Why?
You need to build only 1 request
You don’t need to deal with headers and params
The authorization details need to be entered only once
It is easy to refresh the token when expired
Proceed as follows:
1. In SAP Cloud Platform, Cloud Foundry Environment, go to your BETA enabled subaccount
2. To view the OAuth relevant information, do the following steps:
– Go to your space with the xsuaa service instance for Backend service
– Go to “Services” -> “Service Instances”
– Click your instance
– Click “Service Keys”
– Select your service key
– take a note of the values of the following properties.
In my example, with slightly obfuscated values:
url | https://subaccount.authentication.eu10.hana.ondemand.com |
clientid | sb-baas_access!t55555 |
clientsecret | 2tht5x7yCBSVnFdGp08lijlMojo= |
3. To view your API URL, do the following steps
– Go to your Backend service cockpit and choose the API you wish to call
– copy the URL of the API (OData service)
– change the prefix of the URL
from:
https://yoursubaccount-backend-service.cfapps.xx.hana.ondemand.com/oda…
To
https://backend-service-api.cfapps.xx.hana.ondemand.com/oda…
4. To call your API with Postman, do the following steps:
– Open Postman
– Enter the URL of your API, modified as described above
– Click on the âAuthorizationâ tab
– Click on the TYPE arrow
– Click on OAuth 2.0
– the right area is changed, such that it can deal with access token
– Since we donât have an access token, we click the button âGet New Access Tokenâ
– A popup is displayed âGet new access tokenâ
Here we enter the required info, based on the properties which weâve noted above:
Token Name | any name of your choice, used to distinguish multiple tokens Leave it empty if you need only one |
Grant Type | Password Credentials |
Access Token URL | append /oauth/token to the value of the <url>-property e.g. https://subacc.authentication.eu10.hana.ondemand.com/oauth/token |
Username | here you have to enter the user you use to login into SAP Cloud Platform. In a trial account, it is your e-mail |
Password | the password of the user above |
Client ID | the value of the <clientid> property above, e.g. sb-baas_access!55555 |
Client Secret | the value of the <clientsecret> property above, e.g. 2skt7w1yCFJVnFdGp06lijlMojc= |
Scope | not required |
Client Authentication | Basic Auth header |
Note:
If you try different REST client and grant type “Password” is not supported, then it doesn’t help to try e.g. “Client Credentials”, it doesn’t work, it is a different OAuth flow.
In my example (slightly obfuscated):
– Afterwards click on Request Token
– As a result, you get a popup containing the token (in a list along with other tokens, if you have requested more)
– No need to copy, just scroll down and press âUse Tokenâ to use this token in your main Postman request
– The token is inserted into the “Token”-field and the Postman request is ready to be sent
Thatâs it, the request is successful.
No need to add more screenshots, so you have to believe it
Summary:
In Postman, create one request to call API (with modified prefix), choose “OAuth 2.0”-Authorization, use the tool-support to set the access token.
Appendix 2: Snippets
For your reference, please find below the relevant copy&paste snippets
Parameters for creation of xsuaa service instance
{
"xsappname": "baas_access",
"tenant-mode": "dedicated",
"description": "Security profile of called application",
"scopes": [
{
"name": "uaa.user",
"description": "UAA"
}
],
"foreign-scope-references": [
"$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).Admin",
"$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).AllAccess"
],
"role-templates": [
{
"name": "Token_Exchange",
"description": "UAA",
"scope-references": [
"uaa.user"
]
}
]
}
Parameters for the token-fetch
<Properties> and their values, to be copied from service key of xsuaa instance.
user credentials for grant type: SAP Cloud Platform user
Request URL:
<url>/oauth/token?grant_type=password&username=xx@yy.com&password=xxx
Authorization:
Choose “Basic Authorization”
Credentials:
user: value of <clientid> property
pwd:Â value of <clientsecret> property
Header:
Content-Type: application/x-www-form-urlencoded
API-call
URL prefix: https://backend-service-api…..
Authorization: Bearer <yourtoken>
Appendix 3: obtain token with REST client
In above description we used the browser to fetch the authorization token.
It appears to be simpler: less cryptic, less settings, more user interaction
However, for those of you who are interested, let’s see the cryptic way of obtaining the token with REST client
It helps to complete the picture
Compose request
There are 2 possibilities:
1. Like above, use GET request and pass the parameters in the URL
2. Alternatively, use POST request and pass the parameters in the request body
Letâs use the second option here
In the REST client:
Enter the URL of the authorization server, as described above in this blog
<url>property with suffix /oauth/token
Choose POST as HTTP verb
In the âAuthorizationâ Tab, choose âBasic Authâ and enter the credentials as described above in this blog:
Username is the clientid property
Password is the clientsecret property
Optionally, press âPreview Requestâ
Go to the “Headers” tab and enter the following header:
Content-Type:application/x-www-form-urlencoded
Note:
How to enter Basic Authentication in raw header section:
In case you need a raw authorization header, you have to use online base64-encoder to encode your password, then enter as follows:
Authorization: Basic c2ItYmFhEzMDIwOjJyaHQ1dzN6Q0JIVW5GZEdwMDhsaWpsVW9q . . . Yz0=
Below screenshot shows the raw headers:
Go to the “Body” tab and enter the OAuth grant type in the request body as described above in this blog.
Example:
grant_type=password&username=my@email.com&password=123
Press âSendâ and receive status 200 and the access_token in the response body:
Copy the value of the token without the â
E.g.
eyJhbGciO . . . iJSUzI1NiIsImprdSI6Imh0dHBz . . .Oi8vYn. . . rZW5f
Then proceed calling your API as described above in this blog
Appendix 4: Hiding the password
If you’re concerned because in this explanation we’ve mentioned that the password is visible, as it is sent in plain text over the net:
Let’s see 3 possibilities to at least hide your password:
- Easy:
Use the postman tool support, where the password is hidden. See appendix 1 - Less simple:
Use refresh token. First obtain the token as usually, then use the refresh token, such that password is not required anymore. Explained in the next blog’s appendix - More complex:
Use grant type “Authorization Code“.
In that case, the password is typed in the login screen of XSUAA, so the password is hidden and never sent in plain text
Great article! Thanks Carlos!
Moltes gracies, Enric đ
Hey Carlos! Thanks so much for following up on my request. Just took me a while to try this out. But this is where rainy public holidays are ideal for I guess!
Worked like a breeze for me!
As with the little included Postman class, I'd like to add that environments in combination with test can play a very nice role as well. Using a test you can e.g. pull the access_token from the first request, push it into the environment and then use it in the next request using e.g. {{access_token}}. Also works pretty nicely if you want to conceal passwords in a demo.
Anyway, thanks so much again! This is a very helpful blog!
Hey Carlos,
Nice article.
I tried following all the steps. While I am getting the authentication token and the entire flow is running properly, I just cannot change the value of expires_in which is 43199 by default.
How do I change that to some other value, let's say 5 minutes (300 seconds) ?
âHello  Souvik Majumder ,
Thanks very much for your feedback!
The value of expires_in can be controlled via configuration of the XSUAA instance:
âWhen you create the instance of XSUAA service, you pass a couple of parameters in JSON format, right? Here you can add one parameter for the oauth configuration, which in turn contains one parameter for the validity of the access token (and also for the refresh token):
I've described it a bit here:
âhttps://blogs.sap.com/2019/05/09/sap-cloud-platform-backend-service-tutorial-15-security-using-authorization-code-grant/
and the reference of the parameters of XSUAA configuration can be found in the ââSAP Help Portalâ
Hope this answers your question,
Kind Regards,
âCarlos
â
â
Hey Carlos,
This are indeed the tutorials which encourage me to give the SCP a shot. Excellent work!
You described how to call an external API based on oDATA Service.
https://backend-service-api.cfapps.xx.hana.onâŚd.com/odatav2/DEFAULT/SRV
Do you have an example how to call it in case of nodejs in combination with the framework express?
Many Thanks,
Fouad
Hello Fouad Sebbane ,
Thank you so much for the nice feedback - which in turn encourages me to continue sharing info đ
Yes, I did the same next step, to call an oauth-protected service with node.js application.
This example uses express:
https://blogs.sap.com/2019/05/23/sap-cloud-platform-backend-service-tutorial-20-api-called-from-internal-node.js-with-binding/#code
Please refer to this overview page, where you can find the different approaches to call such API from node:
https://blogs.sap.com/2019/02/19/sap-cloud-platform-backend-service-overview-of-blogs/
Hope it helps you!
Cheers, Carlos đ
Hi Carlos Roggan,
thanks for the nice blog. Really helpful!!
What is the reason, why the manual way only works when BETA features are enabled? (With postman I got it working without BETA features enabled.)
Cheers,
Simon
Hi Simon Roloff Interesting point, I've no idea, I think there shouldn't be any difference.
And thanks very much for the nice feedback đ
Cheers,Carlos
Hi Carlos Roggan ,
Small question, Youâve asked us to change the url and make it a generic one(from the looks of it) which donât have our subaccount name. What happens if two users have created odata with the same name, how will it get differentiated? (for one of my service I am using the same name âFACETSERVICEâ as yours)
** I am assuming it is because of the token which has the clientid, url and other info.. But let me know if it is something else.
Thanks,
Mahesh
Hi Mahesh Kumar Palavalli ,
Again a really good point which you - an experienced user - have pointed out ! đ
Backend Service is a multi-tenant-aware application. From my side, I'm not so multi-anything-aware, so I don't know the technical Details. đ
But I guess that somehow the Backend service stores metadata about every Service in separate schemas. By no means it should be possible that a user accesses secret data just because the Service Name is equal.
If you look into a JWT token, you'll see that it contains info like the identity Zone and issuer, etc, so the Backend Service can distinguish.
Thanks and have a nice Weekend!
Carlos
YThanks for the reply Carlos Roggan , Yeah Iâve followed your another blog and checked it, the token has all the information. Itâs first time for me to see such a non unique URLâs but still they are unique by using JWT.
Enjoy the weekend!!Â
Hi Carlos Roggan,
Thanks for the blog.
But I have a question.
I want to call the backend service on behalf of nobody(No login user). So I choosed [Client Credential] instead of [Password] to get the breaer token.I got the token but the scope is just [uaa.resource].
I want to add some default scopes to the scope above to run my backend service.
Otherwise I just got 403 Response Fobidden.
So do you know how to solve this problem...
Thanks
Hello Bin Xi ,
I haven't tried it, but it is a good question.
Could you please check here, if this solves your problem?
https://blogs.sap.com/2020/06/02/how-to-call-protected-app-from-external-app-as-external-user-with-scope/?update=updated#quickguide
Section B would match your scenario, right?
Please let us know if that works.
Otherwise, we might have to wait for BS to add support for client-credentials-folw
Thanks, Carlos
Hi Carlos Roggan ,
I am unbale to generate the token for the custom IDP(SAP IAS) using the user name and password of a user in the same custom IDP.
But, when tried with default it is working fine.
I am trying it in SAP BTP
Thanks in Advance.
Hi varun bhargav ,
thanks for the interesting comment - actually, I'm not familiar with custom IDP and IAS, but I think that there's quite a probability that password grant type is not supported.
Kind regards,
Carlos
Hi Carlos Roggan ,
THanks for writing this detailed explanation.
I am using SAP BTP Document Management System Integration Option, this requires to call CMIS API call to onboard a DMS repository i.e to connect to a repository, create folders etc.
I am able to obtain JWT Authorization tokens using POSTMAN client, but now I want to make it dynamic and get JWT Auth Token programmatically in SAPUI5 built in SAP BAS.
When I am making AJAX call to my URL https://...authentication.../oauth/token?grant_type=password&username=your@gmail&password=123 I am getting CORS issue, can you help in correcting below snippet?
-Regards,
Rauf
Hi Rauf Shaikh ,
This is rather a UI5-related question, right? I don't know what is the recommended way, how external calls should be done from ui5 code.
- I assume, the typical option to avoid CORS, is to use destinations, isn't it?
- Also, you could consider moving the logic to the service, which is used by your frontend. So it is easier to get the data from your ui5. Does that make sense?
One more question: is it really necessary to use password_credentials to do your call? Usually, for service-to-service communication, client_credentials is sufficient. So you don't need to send user/pwd over the net. That could be read by anyone
Kind Regards,
Carlos