Technical Articles
Application to Service User Token exchange
Cloud application development includes developing multi-tenant applications. Tenants or users access the application using the tenant specific URL. Multi-tenant application manages data securely per tenant without sharing one tenant information to other and is done from a single code repository.
This blog post details out how a multi-tenant application using a re-use service or a service from marketplace can exchange the user token with the consuming custom service with the tenant context in SAP cloud foundry environment.
The challenge here is application and the re-use service are deployed in different provider accounts and so the token forwarding won’t help. Also application to application SSO also doesn’t work as they are in different accounts. So the solution proposed here makes use of OAuth to User Token Exchange flow.
Architecture
Service can be either a cloud foundry market place service or any second application which is deployed in a different provider account.
Step 1: Create the Destination
A destination of type needs to be created in the Application’s provider account and the configuration for the destination is as shown below,
URL: The url of service instance which is binded to the application
Authentication: This must be OAuth2UserTokenExchange
Client ID and Client Secret are the master client ID and Secret of the consuming service or Application (second application). In case it is market place service instance, a service key can be created and the client id and secret of the service key is used here.
OR
If it is second application in a different account then a clone of the second applicaiton’s XSUAA can be created using below API.
Clone Creation using XSUAA REST API
API URL: https://<Application2_authentication_host>/sap/rest/broker/clones?orgid= <Provider_Account_A_Org_ID>&serviceinstanceid=<any_name>
Method: POST
Request Body:
{
“xsappname”: “<any_name_for_clone>”
}
Authentication: Bearer token of Application2 master credentials
Token Service URL: This will be with tenant sub domain placeholder,
https://{tenant}.authentication.sap.hana.ondemand.com/oauth/token
Step 2: Create a Destination Service Instance
In the Application’s provider account space, go to Service Market Place and look for Destination Service.
Create an instance of the Destination Service with all defaults, no special configuration and bind the service instance to the Application.
Step 3: Destination look-up for token exchange
The user JWT received in the Application contains only Application specific details, like this JWT will have scope information of only current Application, so using this JWT token any other service or other application cannot be accessed. So we need to perform OAuth to User Token exchange against the consuming service or second Application. Here the destination service is used to do the token exchange.
Destination look-up HTTP API needs to be called to get the exchange token which can be used to access service or the second application whichever is configured in the destination.
Let’s first get the bearer token required to do destination look-up.
Token URL: https://<application_provider_sub_domain>.authentication.sap.hana.ondemand.com/oauth/token
Client ID: Destination Service Client ID (which you get after binding destination service to application)
Client Secret: Destination Service Secret (which you get after binding destination service to application)
Response will provide a access_token of type bearer, which is used in the next step for destination look-up.
———————————————————————————————————————-
Call below Destination Service API to do destination look-up.
URL: https://destination-configuration.cfapps.sap.hana.ondemand.com/destination-configuration/v1/destinations/<name_of_the_destination>
Authorization: Bearer token of the destination service (which we got from above step)
X-user-token: This is the additional header which has to be passed which contains the user JWT received by application
Response will look like below,
{
"jti": "14b66fa23066414ab52d1e0ff3185120",
"ext_attr": {
"enhancer": "XSUAA",
"zdn": "app-sub"
},
"xs.user.attributes": {},
"granted_scopes": [
"openid",
"application2!b11236.App2.Administrator"
],
"xs.system.attributes": {
"xs.rolecollections": [
"Application1Role",
"Application2Role"
]
},
"given_name": "ABC",
"family_name": "XYZ",
"sub": "2dc83bee-43e9-4f25-842d-28c51a81e8bb",
"scope": [
"openid",
"application2!b11236.App2.Administrator"
],
"client_id": "sb-application2!b11236",
"cid": "sb-application2!b11236",
"azp": "sb-application2!b11236",
"revocable": true,
"grant_type": "user_token",
"user_id": "2dc83bee-43e9-4f25-842d-28c51a81e8bb",
"origin": "ldap",
"user_name": "abc.xyz@sap.com",
"email": "abc.xyz@sap.com",
"auth_time": 1584941963,
"rev_sig": "de7228a2",
"iat": 1584941963,
"exp": 1584985163,
"iss": "https://app-sub.authentication.sap.hana.ondemand.com/oauth/token",
"zid": "b09a0e51-209a-4d86-bae5-ad712f01b76c",
"aud": []
}
Now this token which is received as response of the destination look-up can be used for accessing the consuming service or application2 with subscriber user’s context.
Hi Pradeep,
What you describing is exactly what we need.
We are using the SAP CLOUD SDK 3.25.0 . But if we start the application it crashes.
- com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Failed to get destination with name
‘DPG-DEST’.
- com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Failed to get destination.
- com.sap.cloud.sdk.cloudplatform.security.exception.TokenRequestFailedException: Failed to determine cache key.
- com.sap.cloud.sdk.cloudplatform.security.principal.exception.PrincipalAccessException: Could not read a principal from
neither a given JWT nor a given Basic Authentication header.
Given the code to execute a get query to another OData service in CF:
…
final ErpHttpDestination httpDestination =
DestinationAccessor.getDestination(destination).asHttp().decorate(DefaultErpHttpDestination::new);
final HttpClient client = HttpClientAccessor.getHttpClient(httpDestination);
final ODataRequestRead readRequest =
new ODataRequestRead(url, remoteEntity, prepare(remoteEntity, ""), ODataProtocol.V4);
final ODataRequestResultGeneric result = readRequest.execute(client);
final Map<String, Object> resultMap = result.asMap();
return parseToEntities((List<Map<String, Object>>) resultMap.get(“value"));
Can you help or do you have a small example project.
regards Erik
Hello Erik, did you solve your issue?
Hey Deian,
No still no progress.
Any ideas 🙂
Hey Deian,
We have it running by using foreign-scopes.
regards Erik
Hi. Palmen.
I met the same issue as yours.
Can you share more details or instructions about solution?
Thanks a lot.
Hi Louis,
Sorry for the late reaction.
I followed this blog.
https://blogs.sap.com/2020/06/02/how-to-call-protected-app-from-external-app-as-external-user-with-scope/#samplecode
regards Erik
Hi!
Thanks for this!
I was just missing the X-user-token header name, that I could not find anywhere!
BR,
Rafael