Quicklinks:
Simplified Flow
Detailed Flow
Reference
Refresh Token
Use Tool
This blog is part of a
series of tutorials explaining the usage of
SAP Cloud Platform Backend service in detail.
In the previous blog postings, I’ve already explained how to call a Backend service API from an
external tool (REST client), and how the
OAuth security mechanism works in detail.
In both blogs we were using the OAuth grant type called “Password Credentials”, because it is a bit simpler.
In the present blog we’re going to use the grant type “Authorization Code”, which is more secure.
Note:
In this scenario, the end user's password is never visible, it is not sent as parameter
Prerequisites
Create API in SAP Cloud Platform Backend service.
Create XSUAA instance in your subaccount
Background
Roughly speaking, during the OAuth flow, the
Client (e.g. web application) connects to the
Authorization Server in order to get an access token on behalf of a user.
The
Authorization Server grants access, if the user agrees, and sends a valid token. The
Client uses this token to call the protected API.
The
Authorization Server grants access only if some validations are successful (e.g. password, roles).
If the
Client uses the grant type “Password Credentials”, then the Authorization Server returns the requested token. As
explained before.
If the
Client uses the grant type “Authorization Code”, then the process is a bit different. As explained below.
OAuth 2.0 Flow Overview
Below diagram depicts the OAuth 2.0 flow in a scenario where the grant type
Authorization Code is used.
- The user opens an app (usually a web application, in our case the REST client)
- The app sends a request to the Authorization Server which displays his particular login screen
- The Authorization Server sends an authorization code (back) to the client app
- The client app sends a request to the Authorization Server, including the authorization code
- The Authorization Server responds with an access token
- The client app uses the access token to call the desired API
Note:
In case of “Password Credentials”, the login popup is showed by the
Client
This implies that the client app gets to know the user password, so it must be a trusted app.
In case of “Authorization Code”, the login popup is showed by the
Authorization Server
If above steps sound confusing to you, consider dividing them as follows:
1. Fetch Code:
User opens client, logs in to Authorization Server, which sends code to client
2. Fetch Token :
Client uses code to obtain token from Authorization Server, on behalf of user
3. Call API
Client calls API using the token
This structure makes clear, what’s the difference and benefit, comparing to the “Password Credentials” grant type:
The client does never get to know anything about the user’s password. Instead it deals only with the
Authorization Code which expires immediately.
Drawback: the client needs to be able to receive incoming request. See below.
I've described the OAuth flow 2 times, one is easier and faster, the other one more interesting.
Both do work and are almost the same.
Simplified API call using “Authorization Code” grant type
In this description we ignore the redirect-uri.
0. XSUAA
Create a new instance of XSUAA service as
described here or reuse an existing instance.
Anyways, take a note of clientid and clientsecret.
For example:
"clientid": sb-yourinstance!t12345
"clientsecret": PBJvwKn1OuL51zgisYGLZPYqxa0=
1. Fetch Authorization Code
First step in the OAuth flow is:
The end user opens the client application (in our case: the REST client) and is forwarded to the
Authorization Server which displays a login screen, where he enters the credentials.
So for us, the first step is to call the
Authorization Server to get the
Authorization Code.
This request can be done in a browser window.
We compose the request as follows:
Compose URL
As
already known, use the
url property from service key of xsuaa instance.
Append the endpoint which is responsible for issuing an authorization code:
oauth/authorize
Note:
You may refer to the OAuth 2.0
spec
Add these parameters:
&response_type=code
&client_id=<yourclientidFromXSUAA>
URL template:
https://<subacc>.authentication.<...>/oauth/authorize?&response_type=code&client_id=<id>;
Example URL:
https://acc.authentication.eu10.hana.ondemand.com/oauth/authorize?&response_type=code&client_id=sb-myuaa!t12345
Call URL
Invoke the URL in a browser.
You get a login screen.
Note:
For Trial users:
Below screenshot shows that the "user" is an email, so we enter the mail of our Trial user (not the userid "P12345..." )
Note:
If the logon is not shown, then the browser has probably cached the credentials. If these credentials are ok, you can just continue, otherwise you should clear the browser cache
Below screenshot shows that the logon screen is sent by the XSUAA instance:
Enter the credentials and press the "Log On" button.
The result is an error message, but don’t panic: it is expected (details in below section)
Just ignore it and have a close look at the URL:
The
Authorization Server has tried to send the
Authorization Code to a default server address which doesn’t exist.
BUT the good news: the URL contains the
Authorization Code
Take a note of the code , e.g.
ABcNUi2t3O
2. Fetch Access Token
OK.
Now we have the code.
Now we need the token.
Fetching the token is similar like described in a
previous blog
Compose URL
The endpoint for
oauth/token as described in a
previous blog plus the following parameters:
grant_type=authorization_code
&code=<yourCode>
&client_id=<yourClientId>
URL template:
https://<subacc>.authentication.<...>/oauth/token?grant_type=authorization_code&code=<cod>&client_id...;
URL example:
https://subacc.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=authorization_code&code=RUh4otudtP&client_id=sb-myuaa!t12345
Note:
You see, the difference to grant type "Password Credentials" is that here we don't add credentials to the request. Instead, there's only the "code"
Call URL
In this example, we do the call in a REST client, which allows to enter
clientid and
clientsecret as username and password, choosing
Basic Authentication.
Such request can be stored and automated.
The request should look similar like this:
And the result should look similar like this:
Note:
The AllAccess scope must be listed, as marked in the screenshot, otherwise the token is not valid
Note:
This request can be done in a browser window. It will prompt for credentials, where the clientid and clientsecret have to be entered manually
Note:
Troubleshooting:
While copy and pasting, make sure that there aren’t any blanks in the URL. A convenient reason for errors
If you get an error response, make sure that you properly copied the
clientid and
clientsecret
Try requesting a new
authorization code, which expires after unsuccessful connection
Copy the token (as shown above, without the inverted commas)
3. Call API
Open a new REST client tab
Enter the URL of the API, with modified prefix (see
previous blog to learn how to modify the API URL)
Authentication:
- Choose Authentication type as “Bearer token”, if supported by your REST client
Enter the copied token
- If your REST client doesn’t support that authentication type, then it is still possible to compose the authentication as header:
Header name: Authorization
Header value: bearer eyJhsdfger...etc
Note that the word "bearer" has to be typed before the token is copied
This is shown in the screenshot below:
Finally, press "Send" and get the expected payload from the OData service (your Backend service API)
Detailed API call using “Authorization Code” grant type
In this description we create a dummy local server in order to follow the redirect.
0.1 XSUAA
Create a new instance of XSUAA service with the following json parameters:
{
"xsappname": "forAuthCodeWithScope",
"foreign-scope-references": [
"$XSAPPNAME(application,4bf2d51c-1973-470e-a2bd-9053b761c69c,Backend-service).AllAccess"
],
"oauth2-configuration": {
"token-validity": 7200,
"redirect-uris": [
"http://localhost:3000"
]
}
}
Description:
“foreign-scope-references”:
This is the scope which is
required by Backend service.
The user needs the “AllAccess” role, otherwise he isn’t allowed to call APIs
As a consequence, the token issued by XSUAA, needs to contain this foreign-scope-reference
“oauth2-configuration”:
This parameter isn’t mandatory
"redirect-uris":
Here we can enter a kind of white list of trusted valid uris or hosts which are allowed to receive the “Authorization Code”
For security reasons, it makes sense to specify the “redirect-uri” in the XSUAA configuration.
Like this, the
Authorization Server will deny to follow malicious redirects.
Note: “The
redirection endpoint URI MUST be an absolute URI”
"token-validity":
Here we can specify how long the access token should be valid, before it expires and needs to be fetched again.
The default is 43200 which corresponds to 12 hours
Note:
There's another parameter for "refresh-token-validity"
After creation of service instance, create a "Service Key" and take a note of id and secret
0.2 Local server for redirect
An important topic which we ignored in the simplified description above:
The end user is prompted for login by the
Authorization Server.
After successful user login, the
Authorization Server calls the redirect-uri, to send the
Authorization Code.
That’s why it the "Authorization Code" scenario requires that the client is capable of receiving incoming requests (like mentioned above. If not possible, then a different grant type has to be used).
In our simplified description above, we just ignored that.
Ignoring was possible, because we can see the URL which is attempted to be invoked (see screenshot above)
Now, in this detailed description, we’ll quickly create a local server, allowing us receive the request.
Quickly creating a server is possible with
node.js
If you’re new to node.js you can follow the
description here.
Steps:
Install
node.js
Install
express
In your command prompt, navigate to a folder of your choice, e.g. tempserver
Execute the following command:
npm install express
Check your tempserver directory: a subfolder called "node-modules" has been created
Still in the same folder, create a text file with a name of your choice, e.g. "server.js"
Open the file with any text editor, e.g. Notepad
Enter the following content:
const express = require('express');
const app = express();
app.get('/myoauthclient', function (req, res) {
res.send(req.query.code);
});
app.listen(3000, function () {
console.log('=> Server running on port 3000');
})
Please forgive the minimalistic code.
What it does:
With the help of the “express” library, a server is started, it listens on port 3000
Furthermore, a REST endpoint is defined. Whenever this endpoint is called, the query parameter is extracted and returned in the response.
That’s all.
Save and close the file.
In the command prompt, still in the same folder (same folder like server.js and node-modules), run the following command to start the server:
node server.js
After one second you can see the output in the console:
=> Server running on port 3000
You can test your server by opening a browser and invoking the following URL:
http://localhost:3000/myoauthclient?code=123
Result:
The browser shows the number 123
Note:
To stop the server press the keyboard shortcut
Ctrl+C in the command line. If it doesn't stop the process, press the shortcut 2 times
Note:
The above note was just a note, just in case you want to shutdown the server some day
But not now.
Now we need the server, so keep it running and ignore above note.
1. Fetch “Authorization Code”
This is the request to exchange credentials for Code
This step is the same like described above, just one little difference:
the URL
Compose URL
Compose the URL as described above and add the following parameters:
&response_type=code
&client_id=<yourClientid>
&redirect_uri=<yourClientUri>
Template URL:
https://<subacc>.authentication.<...>/oauth/authorize?&response_type=code&client_id=<id>&redirect_ur...;
Description of the parameters:
client_id:
This param is required, because the
Authorization Server validates if the calling client is registered and valid
Note that no client_secret has to be sent
response_type:
Value is “code”.
This ensures that the “Authorization Server” reacts with sending the “Authorization Code”
Note:
This parameter identifies that the grant type “Authorization Code” is being used.
Because in case of grant type “Implicit”, we would enter “token”, to get the token immediately
redirect_url:
This param tells the
Authorization Server to send the “Authorization Code” to this url.
In our tutorial we decided to enter only the host in the xsuaa, so we’re more flexible with possible endpoints, but still having enough security.
So we specify the full "redirect-url" in the parameter of this get-code-call
Note:
We aren’t sending the “scope” parameter, because we have specified it in the settings of XSUAA instance.
Since the Backend service requires the “AllAccess” role, we’ve entered it in the xsuaa
Example URL:
https://subacc.authentication.eu10.hana.ondemand.com/oauth/authorize?&response_type=code&client_id=s...
Call URL
Invoke the URL in a browser.
You get a login screen (like described above), enter your SAP Cloud Platform user credentials, e.g. the email of your Trial user
After successful login, the
Authorization Server calls our redirect URL and adds the
Authorization Code to the URL as query parameter
Our local node.js server receives the request and responds with sending the code and that is what we see in the browser: the “Authorization Code”
Below screenshot shows the response in the browser.
Example redirect URL:
http://localhost:3000/myoauthclient?code=7bQRR4Dn55
It is the redirect-URL of our local server and the query parameter with the
Authorization Code.
Our local server implementation sends the
Authorization Code as response to the browser, but that’s just convenience, it is not specified in the OAuth 2.0 spec
Note:
The
Authorization Code lives shortly, it has to be requested again after it was used once
Note:
If you get an error message: “no client with requested id: …” then there might be a blank in the URL, due to copy&pasting the clientid
Or you might have to clear the browser cache, in case an old client is cached
Note:
Before doing the redirect, the
Authorization Server validates the given parameter and values, and of course also the entered credentials of the cloud platform user
Note:
If you open the debugger tools of your browser you can see that the
Authorization Server has done 2 requests:
Our original request to the XSUAA has been redirected to our local server, status code 302 “Found” is used like 307 “Temporary redirect” and the “Location” header displays the redirected URL, as desired by us:
Optional Test:
Invoke the code-fetch-URL with a different redirect server, e.g.
&redirect_uri=http://localhost:3001/myoauthclient
As a result, the Authorization server responds with an error message: “invalid redirect”
Now try to fetch the code and send an invalid scope.
For instance, append the following parameter to your get-code-URL
&scope=admin.changePWD
The result will be an error message telling that the scope is invalid.
This means that a malicious client won’t get a role which would allow to do any harm to the backend resources.
These are examples for the benefit of OAAuth 2.0 and “Authorizatino Code” grant type
2. Fetch “Access Token”
We call the token-endpoint of the
Authorization Server.
This time we have to sent the
redirect_uri parameter, although no redirect has to be done.
But it is required, because the
Authorization Server checks if the uri is the same like in the previous request
Compose the URL
Template URL:
https://<subacc>.authentication.<...>/oauth/token?grant_type=authorization_code&code=<code>&client_i...;
Example URL:
https://subaccount.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=authorization_code&c...
Open the URL in a new browser tab (you might need the first tab to re-fetch a valid code).
The credentials of the client are required, so the browser displays a popup
Note:
Of course, this request can be done in a REST client, so the client credentials can be entered as
Basic Authentication
The
Authorization Server validates if client and redirect match the stored settings, if the
Authorization Code is valid and corresponds to the given
client_id and of course if the
client_id credentials are correct
As a result, the server sends the “access_token” in the response:
The response payload contains the following properties:
access_token:
This is what we need to copy, but without the inverted commas
token_type:
bearer: this value is needed as well, it has to be entered in the Authorization header when sending the request to the
Resource Server
expires_in:
Here we can see that the value which we entered in the XSUAA settings has been considered
scope:
Same here: we can see if the role which we need to access the Backend service APIs has been assigned to the access token (remember, we specify this special required scope in the xsuaa)
refresh_token:
This token can be used to refresh the access_token. The difference is that the refresh token lives much longer.
Remember that the validity duration can be configured in the XSUAA settings
3. Call API
Use the
access_token just like described before
Note that the
Resource Server does validation of the access token. The token mustn’t be expired and must contain a valid user who has the valid necessary role(s).
Note:
Troubleshooting:
If you get an error message, try fetching a new
access_token.
Check if the full token is copied&pasted into the API calling tool
Check if the token type ("bearer" or "Bearer") is contained in the value of the
Authorization request header
Check the token-response if the required scope (
AllAccess) is contained.
Check if the end user is a member of the subaccount in which the Backend service is deployed
Check if the end-user has the required role assigned via role collection (see
description)
Check if the role collection is known to the Identity provider (see same
description)
Summary
In this blog we've gone through the flow of an authorization scenario based on OAuth 2.0 using the grant type "Authorization Code".
This means that the
Client needs to fetch a
code before it can fetch a token. That token can then be used to call APIs created in
SAP Cloud Platform Backend service
Links
See
previous blog
Appendix 1: Quick Reference
- Get Authorization Code
https://<subacc>.authentication.eu10.hana.ondemand.com/oauth/authorize?&response_type=code&client_id...
- Get token
https://bssubaccount.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=authorization_code...
- Call API
https://backend-service-api.cfapps.eu10.hana.ondemand.com/odatav4/DEFAULT/MYSERVICE;v=1/PRODUCTS
- Refresh token
https://<subacc>.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=refresh_token&refresh_...;
Appendix 2: How to use the refresh token
In order to get an
access token, the
Client has to send a request including the
Authorization Code.
However, since the
Authorization Code expires immediately, the client has to send previously a request to get the code, which implies that the end user has to enter his credentials.
This 3-step-process can be simplified by using a
refresh token.
This
refresh token is sent along with the
access token and duration of validity can be very long.
It can be used multiple times to fetch a new
access token, once that is expired.
The
refresh token doesn’t require a login from the end user. Only the client has to authenticate with
client_id and
client_secret.
Compose the URL
The
refresh_token request is sent to the
oauth/token endpoint of the corresponding XSUAA instance, like the
access_token request
The parameters:
&grant_type=refresh_token
&refresh_token=<yourTokenFromPreviousRequest>
&client_id=<yourClientId>
Template URL:
https://<subacc>.authentication.<...>/oauth/token?grant_type=refresh_token&refresh_token=<token>&cli...;
Example URL:
https://subaccount.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=refresh_token&refres...
HTTP verb:
GET
Authentication:
Basic Authentication using
cliend_id and
client_secret
The request can be executed either with a REST client, or with a browser which prompts for user/pwd
In a
previous blog I described how to use
Postman REST client tool support for OAuth 2.0 calls to Backend service API
The description was based on grant type “Password Credentials”
When using “Authorization Code”, some more details need to be entered.
It should be enough guidance to give a screenshot here, all the rest is the same
Use these template URLs to compose the endpoints
https://<acc>.authentication.eu10.hana.ondemand.com/oauth/authorize
https://<acc>.authentication.eu10.hana.ondemand.com/oauth/token
Note:
Postman displays the login screen of the XSUAA, where you need to enter the end user credentials, e.g. Trial user email