SAP BTP, Cloud Foundry Environment XSUAA Explained
When developing and deploying an application it quickly becomes important to understand how authentication and authorizations work on the SAP BTP, Cloud Foundry environment.
In our tutorials and documentation, we recommend using approuter
as a proxy service to handle authentication management to your implemented application.
The following steps will show what happens behind the scenes.
The requests can be manually reproduced by a REST client of your choice, e.g. Postman or Insomnia.
The following documentation only touches a subset of features of the XSUAA Service on Cloud Foundry. The SAP Cloud SDK and XSUAA are developed independently. We do not provide in-depth support on XSUAA topics beyond SAP Cloud SDK use cases. Mind, if some information seems outdated - get in touch with us and refer to official XSUAA docs.
SAP BTP, Cloud Foundry Environment XSUAA Key Use-Cases
- User Login:
Authorization Code Grant
- SAP BTP Service Usage on behalf of a User:
JWT Bearer Token Grant
- SAP BTP Service Usage on behalf of a service:
Client Credentials Grant
- Resolve User Access Token:
Refresh Token Grant
Read the Application Properties
To create a request, we need to parse the XSUAA connection data.
-
Take note of your "application route". That's the URL for which an authorization request will be built.
-
Open the system-provided environment variables of your application on Cloud Foundry.
-
Extract values
_url_
,_clientid_
,_clientsecret_
from the JSON value, located in the objectVCAP_SERVICES.xsuaa[0].credentials
.
Depending on your setup, the xsuaa
array may have more than one entry.
Because your application can be bound to multiple instances, e.g. through different service plans.
Authorization Code Grant
Since we start without an existing access token, our journey begins with the browser flow of Authorization Code Grant.
This flow is split into two steps:
- Get authorization code on behalf of a single-sign-on login form.
- Get personal access token from authorization code.
Get Authorization Code
You will likely need to run the following HTTP request in your browser and check the HTTP response.
-
Make the following request:
GET https://[xsuaa.url]/oauth/authorize
Query parameters:
client_id=[xsuaa.clientid]
redirect_uri=[application.route]
response_type=codetipOptional values can be set for "scope" and "login_hint". Use
scope=uaa.user
here when facing unexpected "Unauthorized" response for the resulting[code]
in the next request. -
Submit login form via a browser or REST API debugging tools like POSTMAN or Insomnia.
-
Check the HTTP response and extract
[code]
from theLocation
header.HTTP/1.1 302 Found
Strict-Transport-Security: max-age=31536000
Set-Cookie: X-Uaa-Csrf=[...]; Path=/; Max-Age=86400; Expires=[...]; HttpOnly
Cache-Control: no-store
Content-Language: en
Location: [application.route]?code=[code]
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Get OAuth2 Access Token
With the authorization code we can now request a real access token from the OAuth2 service endpoint:
-
Make the following request:
POST https://[xsuaa.url]/oauth/token
Headers
Accept: application/json
Content-Type: application/x-www-form-urlencoded
client_id=[xsuaa.clientid]
client_secret=[xsuaa.clientsecret]
redirect_uri=[application.route]
code=[code]
grant_type=authorization_code -
Check the response:
{
"access_token": [access_token],
"token_type": "bearer",
"id_token": [...],
"refresh_token": [refresh_token],
"expires_in": [...],
"scope": [...],
"jti": [...]
}Congratulation, now you've fetched a valid
access_token
. It can be further evaluated and forwarded.
Some applications like _approuter_
will save the refresh_token
to the user session for you.
This enables the automatic retrieval of new access tokens after the existing one has expired during the active session.
JSON Web Token Bearer Token Grant
Several services on the SAP BTP, require a dedicated OAuth2 access token, e.g. Connectivity Service and Destination Service.
-
Open the system-provided environment variables of your application.
-
In the JSON value, locate the object
VCAP_SERVICES.destination[0].credentials
. Make note ofclientid
,clientsecret
,uri
-
Make the following request:
POST https://[xsuaa.url]/oauth/token
Headers
Accept: application/json
Content-Type: application/x-www-form-urlencoded
client_id=[destination.clientid]
client_secret=[destination.clientsecret]
assertion=[access_token]
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
response_type=token -
Check the response:
{
"access_token": [destination_access_token],
"token_type": "bearer",
"expires_in": [...],
"scope": [...],
"jti": [...]
}Congratulation, you have a valid
destination_access_token
. It can be used to query thedestination.uri
linked destination service on behalf of the current user.
Client Credentials Grant
Some applications require access to a platform service without an active user session, with technical user credentials. For example, reading a list of destinations does not require a user access token. Instead, we can request an access token on behalf of the service binding itself. Here we use the Client Credentials Grant.
-
Make a request:
POST https://[xsuaa.url]/oauth/token
Headers
Accept: application/json
Content-Type: application/x-www-form-urlencoded
client_id=[destination.clientid]
client_secret=[destination.clientsecret]
grant_type=client_credentials -
Check the response:
{
"access_token": [destination_access_token],
"token_type": "bearer",
"expires_in": [...],
"scope": [...],
"jti": [...]
}Congratulation, you have a valid
destination_access_token
. It can be used to query thedestination.uri
linked destination service on behalf of the service binding.
Refresh Token Grant
If the current access token is expired, a new one can be requested with the Refresh Token flow.
-
Make a request:
POST https://[xsuaa.url]/oauth/token
Headers
Accept: application/json
Content-Type: application/x-www-form-urlencoded
client_id=[xsuaa.clientid]
client_secret=[xsuaa.clientsecret]
refresh_token=[refresh_token]
grant_type=refresh_token -
Check the response:
{
"access_token": [access_token],
"token_type": "bearer",
"id_token": [...],
"refresh_token": [refresh_token],
"expires_in": [...],
"scope": [...],
"jti": [...]
}Congratulation, you now have a refreshed
access_token
.