Technical Articles
OAuth2SAMLBearerAssertion Flow with SAP BTP Destination Service. SuccessFactors.
This instalment belongs to a mini series of blogs on the OAuth2SAMLBearerAssertion Flow with the SAP BTP Destination Service. The focus being on how to leverage the destination service’s default x.509 trust with your destination. Good to know:
|
Abstract.
And this is where SAP BTP Destination service comes to the rescue. |
Putting it all together.
Before you can configure the API hub sandbox environment you will need to have created an instance of the destination service on your SAP BTP sub-account. Please refer to the following article on the details for the sandbox environment configuration with SAP API Business Hub. Assuming you have the right level of access to your target SFSF system you may now create there your own OAuth2.0 client application. Let’s call it Quovadis-SFSF. One piece of information you will need from the Destination service created on SAP BTP sub-account level is the trust’s public key that you will need to insert into the Quovadis-SFSF OAuth2.0 client application as depicted below: |
Assuming you will be rehearsing the access to the destination
service APIs with the SAP Business API Hub sandbox environment
you do not need to write a single line of code.
All you need to do is create a definition of your destination
Your destination will be called by the destination
service find api any time you need to procure a bearer access token
to authorize access to remote resource.
You can create a destination definition using the GUI of the SAP BTP sub-account or programmatically calling the destination service APIs.
Let’s rather do it programmatically as follows:
Post (=create) a new destination:
Put (=update) an existing destination:
https://destination-configuration.cfapps.<region>.hana.ondemand.com/destination-configuration/v1/subaccountDestinations
{
"Name": "Quovadis-SFSF",
"Type": "HTTP",
"URL": "https://apisalesdemo2.successfactors.eu/odata/v2/User/$metadata",
"Authentication": "OAuth2SAMLBearerAssertion",
"ProxyType": "Internet",
"tokenServiceURLType": "Dedicated",
"audience": "www.successfactors.com",
"authnContextClassRef": "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession",
"companyId": "<SFSF company code>,
"clientKey": "<apiKey from Quovadis-SFSF application>",
"apiKey": "<apiKey from Quovadis-SFSF application>"
"SystemUser": "<technical user>",
"nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"tokenServiceURL": "https://salesdemo.successfactors.eu/oauth/token"
}
Reference the URL value please refer to the following SAP note
2215682 - Successfactors API URLs for different Data Centers
Most of the values will need to come from the OAUTH2.0 service
which is used to protect the remote resource you are trying
to have your user get access to.
For the sake of simplicity and for sandboxing purposes only
we may use a technical user, a user that must exist in
both the client application and the target backed end system.
In a productive scenario you would rather be using
a user's JWT token instead! This user's JWT token would need
to be passed in the X-user-token header of the destination
service find destination API call.
Then, the destination service will internally generate a saml assertion
for user's authentication with the:
- destination service x509 trust as the assertion issuer,
- audience as the assertion audience (service provider),
- tokenServiceURL as the assertion's recipient (ACS=Assertion Consumer Service)
- and both the apiKey and companyId properties.
This assertion will be used on the IDP-initiated flow with the
recipient acting as ACS (Assertion Consumer Service).
Find destination API call:
https://destination-configuration.cfapps.<region>.hana.ondemand.com/
destination-configuration/v1/destinations/Quovadis-SFSF
where the region would be any of the BTP regions where
the destination service is available.
As a result of this call you will get the bearer access token that you can use to call into the any SFSF OData APIs, your user may have been granted access to, as depicted below: |
__________
Troubleshooting
This is how to troubleshoot a saml assertion that is being generated by destination service: |
Appendix
As of June 2021, SAP BTP destination service features a newly added /saml2Metadata. REST API endpoint allowing you to download the generic SAML IdP metadata (that precisely contains the x.509 certificate) |
https://destination-configuration.cfapps.<region>.hana.ondemand.com/destination-configuration/v1/saml2Metadata
{
"idpMetadata": "PG5zMzpFbnRpdHlEZXNjcmlwdG9yCiAgICAgICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiCiAgICAgICAgeG1sbnM6bnMyPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgICAgeG1sbnM6bnM0PEZXNjcmlwdG9yPgogICAgPC9uczM6SURQU1NPRGVzY3JpcHRvcj4KPC9uczM6RW50aXR5RGVzY3JpcHRvcj4="
}
The resulting idpMetadata string is a base64-encoded generic IDP XML metadata. It does contain the x509 certificate but is not configured for any specific scenario (i.e. CF to CF, CF to Neo etc.) which might require additional properties. For convenience, it the can be decoded into plain XML with any saml assertion decoder and then formatted with pretty print as follows: |
<?xml version="1.0"?>
<ns3:EntityDescriptor xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:ns2="http://www.w3.org/2001/04/xmlenc#" xmlns:ns4="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ns3="urn:oasis:names:tc:SAML:2.0:metadata">
<script/>
<ns3:SPSSODescriptor AuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns3:KeyDescriptor use="signing">
<KeyInfo>
<X509Data>
<X509Certificate> MIIFiDCCA3CgAwIBAgIKl4jx5h0R3MSTdzANBgkqhkiG9w0BAQ0FADCBgjFLMEkG A1UEAwxCY2ZhcHBzLmFwMjEuaGFuYS5vbmRlbWFuZC5jb20vYWZiYWM0ZGUtOWQx D85Qu75kaZW44MwyUDaiWBfvV2MH3dT7MxaLaQ== </X509Certificate>
</X509Data>
</KeyInfo>
</ns3:KeyDescriptor>
</ns3:SPSSODescriptor>
<ns3:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<ns3:KeyDescriptor use="signing">
<KeyInfo>
<X509Data>
<X509Certificate> MIIFiDCCA3CgAwIBAgIKl4jx5h0R3MSTdzANBgkqhkiG9w0BAQ0FADCBgjFLMEkG A1UEAwxCY2ZhcHBzLmFwMjEuaGFuYS5vbmRlbWFuZC5jb20vYWZiYWM0ZGUtOWQx D85Qu75kaZW44MwyUDaiWBfvV2MH3dT7MxaLaQ== </X509Certificate>
</X509Data>
</KeyInfo>
</ns3:KeyDescriptor>
</ns3:IDPSSODescriptor>
</ns3:EntityDescriptor>
Additional reading
Last but not least. Let’s see the rationale behind using the Destination Service.SAP SuccessFactors HXM Suite OData API: Developer Guide (V2) |
Hello Pieter,
Please refer to the Putting it all together section of this blog post.
In a nutshell the access to destination service REST APIs is protected by a destination service own OAuth client.
Assuming you are rehearsing your destination on API Business Hub, first you will need to set up a destination service sandbox there.
Before you can configure the API hub sandbox environment you will need to have created an instance of the destination service on your SAP BTP sub-account.
Please refer to the following article on the details for the sandbox environment configuration with SAP API Business Hub.
I hope this helps. Regards; Piotr
Hi Piotr,
My goal is to find out what issue I have in my destination to SF where the API is returning:
I have an endpoint created in a CAP application to return the JWT of my authenticated user and then I want to use that JWT as the Bearer token to the destinations call in order for it to return the bearer token (destintation 'authTokens' response) that would be used to authenticate with the SF API.
When I use the client id/secret of the destination service instance key and query my 'successfactors' destination via https://destination-configuration.cfapps.eu10.hana.ondemand.com/destination-configuration/v1/destinations/successfactors it returns the following error message for the 'authTokens' property in the response:
Update
uaa.user
Update 2
Hello Pieter,
a. My 1st recommendation would be that you make it work using the API Hub Sandbox to call your destination via find destination API. In other words without your CAP application.
You may refer to this comment for the detailed steps.
This must work before going any further!
b. you might want to share the destination definition by redacting the sensible information...
c. regarding the user JWT token...if you decode it using jwt.io you must be able to see there the SFSF user' identity your are trying to propagate.
And if the user's identity claim in the jwt token is different from user_name then you will need to additionally tell the destination service what claim it is: for instance sub, email, etc...
I hope that helps; Piotr;
Hello Piotr,
Thanks to the API Hub Sandbox I noticed that I need to send the end-user's JWT as X-user-token header value and not as the bearer token. I am now getting the authToken returned that would be send to the SF API (this works both in the API Hub Sandbox or in Postman).
The result of a direct call to the SF API is the same as through the approuter: 403 [LGN0022]Person-only not supported when gace flag is off
This is the base64 decoded (redacted) content of the authToken for the SF API resturned by the destination service:
FYI the user_name of the JWT is correct and matches the SF userId as is required by OAuth in SF.
Hello Pieter,
OK. So it looks like you can retrieve the access bearer token via find destination API call. That's great!
Re the error message: which SFSF ODATA API endpoints you have tried ? is it a GET or POST/PUT API call ? can you tell more about it ?
Otherwise there is a SAP note, namely 2872019 - [LGN0022]The access token is either rejected or expired - SuccessFactors HXM Suite - Can you cross check if this is relevant to your problem?
Hi Piotr,
It's a GET call. I found that note as well, but it doesn't seem to be related.
The the bearer access token for SF contains this 'personOnly:true' and the personGUID instead of the user ID. When I create an access token via postman like it's explained in 2800150 - How to test OAuth authentication via Postman - SuccessFactors Integrations, the access token that is provided does not contain the properties 'personOnly' or 'personGuid' and the 'sfPrinciple' property contains the SF user ID and not the SF person GUID (as shown in my previous comment).
Pieter
Hi Pieter,
OK. The method described in 2800150 relies on the SFSF OAuth2 client idp endpoint to generate the saml assertion with the SFSF user id. This method has two big caveats: it needs the private key of your x509 certificate to be passed in the idp endpoint call and it requires a hard-coded user id.
On a side note: you will likely obtain the same result with the destination service if you add the "SystemUser" property with the user id. (You do not need to remove the other jwt related properties as SystemUser property will override them. Please note SystemUser property is deprecated and may be removed from service any time soon.)
Still my understanding is that regardless of the method you use to obtain the bearer access token you get the same error when calling the ODATA API ? Correct ?
403 error means Forbidden. So this is likely related to your SFSF user permissions.
Even if the aforementioned note SAP note is not that relevant it points to page 30 of the SAP SuccessFactors HXM Suite OData API: Developer Guide (V2). Please have a look if your problem is or is not related to SFSF user permissions.
Have you tried to call the API with sfadmin as user?
Hi Piotr,
My intention is to use the destination and not the idp endpoint. I only mentioned this to indicate the difference in the decoded content of the bearer access token. The one generated by the destination service contains a 'personOnly' and the error returned by the API seems related "[LGN0022]Person-only not supported when gace flag is off".
This error is only received when using the access token provided by the destination service. I really think the issue lies with the destination service (or the successfactors extension service, in case it's still used somehow after the destination is generated by it).
I don't understand where the person GUID even comes from (it's the correct SF person GUID for the provided SF userId (JWT user_name). The SF person GUID is NOT part of the JWT and thus is retrieved somehow by the destination service from SF?!
Pieter
Hello Pieter,
Indeed the destination service will call the SFSF OAuth client token endpoint via tokenServiceURL passing it the saml assertion that it has generated internally [based on the destination definition properties].
What it means that whatever you can see in the decoded bearer access token eventually comes back from SFSF OAuth client.
What you could do is the following:
1.Could you please test with find destination using SystemUser property in the destination definition to pass the same SFSF user id or name as with the SFSF idp endpoint?
(I bet the bearer access token would be like for like as via the internal SFSF idp endpoint.)
2.Could you please explain how you generate the X-user-token ?
3.Could you please provide the destination definition ? not the screenshot from the UI but rather the json output from the get destination API call ?
Thanks;Piotr
PS, not sure about your relationship with SAP; are you a partner/customer?
Hello Pieter,
Long story short: when using find destination with the dynamic business user identity (passed via user JWT token) and if the user JWT token contains the user_uuid attribute, this user_uuid attribute will be added to the internally generated saml assertion. Please note this is by design.
You may refer to this other blog post for more details.
I hope that helps; Piotr;
Hello Piotr and fellow blog readers,
I can confirm that the cause of my issue is related to the presence of the user_uuid in the SAML assertion. The solution was to remove the user_uuid as a (default) SAML assertion attribute in IAS for the BTP application.
IAS would pass the (internal) user_uuid to BTP during SSO, XSUAA would then include the user_uuid in the JWT which is passed to the destination service. The destination service then includes that user_uuid in the SAML assertion it generates. Upon receiving the user_uuid as assertion attribute, SuccessFactors assumes (IMO wrongfully) that we want to receive a person GUID based access token. The access token is then refused when used to authenticate to the OAuth protected OData API.
Thank you Piotr for helping out and finding the solution!
Hi Piotr
Thanks for the blog.
I just read successfactor odata api oauth part.
It need private key of X509 certificate when get assertion.
https://help.sap.com/viewer/d599f15995d348a1b45ba5603e2aba9b/2105/en-US/4e27e8f6ae2748ab9f23228dd6a31b06.html
How could I get private key of X509 certificate of BTP?
Hello Jin Wu,
Thanks for your question.
Indeed, one cannot get the private key of a default BTP destination service trust. That's by design.
But one can bring her/his own key pair to a BTP destination service (as described here) or alternatively one can use the SFSF intrinsic OAuth idp endpoint to generate the saml assertion (as described here).
Furthermore I recommend you have a look at the entire series of blogs I published on this topic:
kind regards;
Hi Piotr,
We have followed the blog and are set up.
However, when I am trying to implement this flow in our CAP Application, I'm getting a problem which can be found in summary here:
I can correctly retrieve the token in my application along with the relevant information about the user i.e. user_uuid, email etc.
Can you spot where we've missed something? Is there a reference we can follow to make this work using SAP Cloud SDK and such?
Thanks,
Erlo
Hi Erlo
What I can spot is the userIdSource property value in the destination definition should be email rather than mail.
I hope that helps; kind regards;Piotr
PS. userIdSource is the value of the user claim from a user JWT token that you want to have propagated in the saml assertion.
userIdSource property default value is
.
Hi Piotr,
Thank you for going through my question. Appreciate you taking the time.
I found out that the issue was that we did not have /IWFND/SG_MED_CATALOG_0002 in our destinations scope list - which had some roles in it.
We have bypassed that error now but we are getting a new error:
Could this be because YY1_GETSERVICEORDERS_CDS_0001 does not have any business catalogs assigned to it?
Note: we are using the bearer access token we get from the destination and we're using it to test in Postman currently.
Erlo
Hi Erlo,
Indeed it might well be the reason of the error.
And if it were the case you might have to either switch to using your communication (==technical) user instead of your business user, cf here
Or to using the client certificate authentication instead, cf here
What is the API endpoint? is it one of the public ODATA APIs or your own crafted ODATA API endpoint?
The info you provided makes me think it is the latter.
If you go to https://developers.sap.com/tutorial-navigator.html and then https://developers.sap.com/group.abap-extensibiliy-cbo-cce-ccl.html there you will find a series of ABAP extensibility tutorials like for instance Create Custom Business Objects in S/4HANA Cloud or
Create the UI for a Custom Business Object where you can find out how to maintain the business catalog for your custom object extension.
cheers; Piotr
PS. Other tutorials:
https://developers.sap.com/tutorials/abap-custom-ui-business-object.html
https://developers.sap.com/tutorials/abap-custom-ui-communication-arrangement.html
Hi,
I have followed exactly like this blog that using the CF's trust, but getting 401 error during the connection test.
Have tried generate the new X509 cert from CF and generate the API key from CF as well, but getting the same error. However, it works when tested from postman after extract the private key using the destination API. Appreciate your help on this.
Hi Ching Hong Chong,
The check connection from the destination definition GUI is not really doing or checking anything (at least if OAuth2SAMLBearerAssertion authentication method chosen).
It is simply trying to call the business call URL, however, without running the destination. That's sth I had reported a while ago; cf. https://blogs.sap.com/2021/03/26/oauth2samlbearerassertion-flow-with-the-sap-btp-destination-service./#comment-565041
In other words to test your destination definition you need first to call the find destination API with your destination definition (and any required headers). From the find destination API payload you will retrieve the access bearer token which you will be able to use to run your business ODATA calls against SF.
I hope that helps; Piotr
PS. You may look up all my SF related blogs here: https://blogs.sap.com/tag/quovadis-sfsf/
Hi Piotr,
Thanks, miss out this important piece information, tested working via destination-API. Appreciate your help on this.
Btw, there is another question, I tried to generate the X509 cert from SF and downloaded as PEM file. However, does not seem able to upload into CF.
sample payload: {
"Name": "SF-TEST-OAUth.pem",
"Content": "Encoded content from step4",
"Type": "CERTIFICATE"
}
Any quick hint on what I have done wrong? Appreciate your help again.
Thank you.
Hi, Ching Hong Chong
Glad you were able to fix your problem.
Re your other question. The error message indicates the keystore you may have created is not recognised or is just missing the private key...
I'd really recommend you follow all the steps from https://blogs.sap.com/2021/04/01/oauth2samlbearerassertion-flow-with-successfactors-with-quovadis-sap-destination. blog to the letter...
Simply try to run the openssl command to create a PKCS12-formatted pfx keystore using the steps from the above blog
I hope that helps; Piotr
PS.
Please do not forget to remove the trailing tags (###xxxxxxxx) from the either key of the pair.
Hi Piotr,
Thanks. I am able to upload the cert now via PEM format due to the mistake, the header have to be "-----BEGIN PRIVATE KEY----- " instead of "-----BEGIN ENCRYPTED PRIVATE KEY-----". As the generated SF file is giving "-----BEGIN ENCRYPTED PRIVATE KEY-----".
Will go through the provided URL again, try with PCKS12-formatted pfx keystore instead.
Just that something I still scratching my head is keep getting error "Error reading certificate SF-TEST.pem: Error reading certificate from keystore SF-TEST.pem" when testing the destination. I have tried generate a new cert from CF destination directly, and apply this cert to my destination, also getting the same error. It only works when using the CF default trust cert. Just too weird, either the message is wrong.
From the screenshot below, it does look like able to read the cert content in encoded base64 format. And the content can be decoded to find out the private key.
Decoded the content above:
Nevertheless, appreciate your reply.
Hi Ching Hong Chong,
As already said the error message from the screenshot is quite verbose. That indicates the keystore you created is ill-formed somehow.
Please let me know how it goes with the PCKS12-formatted pfx keystore instead.
kind regards; Piotr
PS.
Having said that there is a bunch of tools that can help. I personally have been using a tool called portecle
It is an excellent tool that offers plenty of GUI-driven features that should help when playing with keystores and certificates
Hi Piotr,
Thanks again, it is working now if following the PCKS12 format.
Thank you.
Hi All,
Thanks in advance.
pls. check bewlo error, i am facing this while fetching OAuthToken
Hello, please refer to this comment from my sibling blog; I hope that helps; regards; Piotr
Hello Piotr Tesny ,
we have a problem connecting to A SF LMS ODATA service via BTP destination.
https://answers.sap.com/questions/13834740/principle-propagation-between-btp-and-successfacto.html
Not sure what might be the problem...
Hello Tim, please have a look: https://answers.sap.com/answers/13836757/view.html