Skip to Content
Technical Articles
Author's profile photo Piotr Tesny

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:

  • Please consider there are other types of authorization flows that might be a better fit for your application.
  • Using SAML Assertions as Authorization Grants covered in here
Last but not least, please consider reading a sibling blog to see how to leverage the Destination Service with your own key pair (with your own x.509 trust), namely:

Abstract.

The SAML 2.0 Bearer Assertion Flow typically comes into play when we want to give a client application’s users an automated access to remote resources or assets which are protected with the OAuth2.0 protocol.

A common assumption is that the user’s remote resource access scope will be determined by the user’s identity as it is known on the client application side.

Thus the most important aspect of this flow is the propagation of the user’s identity to the backend system commonly referred to as Principal (=user identity) Propagation.

 

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)

 

Assigned Tags

      13 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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

      Author's profile photo Pieter Janssens
      Pieter Janssens

      Hi Piotr,

      My goal is to find out what issue I have in my destination to SF where the API is returning:

      • [LGN0022]Person-only not supported when gace flag is off

      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:

      Retrieving of OAuthToken failed due to the following reason: Cannot determine user to propagate for OAuth2SAMLBearerAssertion destination. Either provide user_token JWT token (https://docs.cloudfoundry.org/api/uaa/version/4.7.1/index.html#user-token-grant) when retrieving the destination or configure it with SystemUser.
      This is expected, so instead I use the JWT of my authenticated user as the Bearer token on the same request URL. This results in a '403 Forbidden'.
      Update
      From the CF docs: the bearer token for this request must have uaa.user
      When I look in the decoded end-user JWT, I don't see the uaa.user in 'scopes'...
      Update 2
      https://launchpad.support.sap.com/#/notes/2876853
      I have updated my UAA instance to include the user.uaa scope and this now shows in the end-users JWT. The destination API is however, still replying with 'Forbidden' when I use the user_token.
      Best regards,
      Pieter
      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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;

       

       

      Author's profile photo Pieter Janssens
      Pieter Janssens

      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:

      {
         "tokenContent":{
            "apiKey":"xxx",
            "sfPrinciple":"AE46BBEE28EC4657A7B47DBECFE70AA0#DIV#instanceT1",
            "issuedFor":"SAP_CLOUD_PLATFORM-SYSTEM-NAME-instanceT1-2021_06_29_06_38_03",
            "scope":"",
            "issuedAt":1625046525151,
            "expiresAt":1625132925151,
            "personOnly":true,
            "personGuid":"AE46BBEE28EC4657A7B47DBECFE70AA0"
         },
         "signature":"QNlIORY7NQy5d52VF3Xf3IHbusJlupBR+WqwN9Qvu/BfdL9zPWpSiRlFz4MAa9FtYDrhnXiaU1wVzJwJZNwaZ8kpDlvLz/DajVxUJ6tN2Letg6jW+D7Lxm3IKshfs0CF5XLeNmRWSXOuEIgMydvQd4B8xNlaloOVIclJUN5v+4k="
      }

       

      FYI the user_name of the JWT is correct and matches the SF userId as is required by OAuth in SF.

       

      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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?

       

       

      Author's profile photo Pieter Janssens
      Pieter Janssens

      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

      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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?

       

      Author's profile photo Pieter Janssens
      Pieter Janssens

      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

       

      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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?

      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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;

      Author's profile photo Pieter Janssens
      Pieter Janssens

      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!

       

      Author's profile photo Jin Wu
      Jin Wu

      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?

       

      Author's profile photo Piotr Tesny
      Piotr Tesny
      Blog Post Author

      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;