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

How to generate SAML bearer assertion token with SAP BTP Destination Service?

Abstract.

We have already seen how to generate a SAML bearer assertion token programmatically with SuccessFactors and Analytics Cloud as well as with S/4HANA on premise

Now it’s time to explore how to offload this task to our polyvalent SAP BTP destination service.


I happened to come across this option a little bit by accident as I was exploring ways to simplify the 
OAuth2SAML2 setup with on premise S/4HANA ABAP server…Indeed, SAP BTP destination service is capable of many things, one of them being SAML Assertion Authentication – page 105

What initially misled me was that in order to be able to select the SAML Assertion Authentication one must choose the OnPremise proxy type [in the destination definition]. And I did not want to have to create a virtual mapping to my S/4HANA ABAP server gateway in any CloudConnector.

But, as it  eventually turned out,  it does work without any CloudConnector and is not even calling the destination URL.

Good to know:

  • You can leverage the destination service as a genuine saml bearer assertion generator.

Disclaimer:

  • Please note all the code snippets below are provided “as is”.
  • All the x509 certificates, bearer access and refresh tokens and the likes have been redacted.

Putting it all together.

To make this exercise more relevant I will be replacing the previously discussed programmatically generated saml bearer assertion with the one produced by the destination service.

 

A. Preparing a destination. Let’s call it SAML-EC_ADM_OAUTH.

  • create a destination definition (either from BTP sub-account UI or with destination service API)
  • Provide x_user_token.jwks_uri property with the URI of the JSON web key set, containing the signing keys which are used to validate the JWT provided in the X-UserToken header.

These are the two main options for getting a user’s JWT token. Additionally, you may need to pass this token in the X-UserToken header of the destination service find API call.

1.

Have the IDP of your ABAP system (QJ9) trust a SAP BTP sub-account.

The BTP sub-account becomes a service provider. Then, when you deploy business applications to any BTP runtime: CF, Kyma etc.

BTP’s XSUAA service – acting as an OIDC provider – will automatically pass your user’s JWT token in every HTTP header back to your application.

Good to know: in this scenario you may not need to pass additionally the JWT token in the X-UserToken header of the destination service find API call.

2.

Alternatively, you may create an OIDC application in the IDP of your ABAP system (QJ9).

Then you can retrieve a user’s JWT token of your currently logged SAML IDP user either implicitly or, explicitly, by calling the OIDC application authentication flow as depicted below:

Here goes an example of a user’s JWT token: eyJraWQiOiJLZ0RMLVQ2NFhuQThoSmFESnlxeGRQZVgwNDgiLCJhbGciOiJSUzI1NiJ9…

 

As my nodejs client application is not necessarily deployed on BTP I have opted the latter approach.

 

B. Generating SAML bearer assertion token

  • find destination and retrieve the base64 encoded token
  • decode the token for verification

Here goes a screenshot from API Business Hub which I recommend to all when it comes to prototyping with APIs

and below is the result of the find destination API call

{
  "owner": {
    "SubaccountId": "xxxxxxxxxx-e2a9-4c17-84ec-xxxxxxxxxx",
    "InstanceId": null
  },
  "destinationConfiguration": {
    "Name": "SAML-EC_ADM_OAUTH",
    "Type": "HTTP",
    "URL": "https://<host>.<domain>:<port>/sap/bc/sec/oauth2/token?sap-client=666",
    "Authentication": "SAMLAssertion",
    "ProxyType": "OnPremise",
    "KeyStorePassword": "<KeyStorePassword>",
    "audience": "QJ9_666",
    "authnContextClassRef": "urn:oasis:names:tc:SAML:2.0:ac:classes:x509",
    "Description": "QUOVADIS-EC_ADM_OAUTH anywhere",
    "KeyStoreLocation": "quovadis_ateam-isveng.p12",
    "clientKey": "EC_ADM_OAUTH",
    "nameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
    "CloudConnectorLocationId": "",
    "x_user_token.jwks_uri": "https://<IDP host>.<domain>/oauth2/certs",
    "userIdSource": "<user claim from JWT token>" // defaults to "user_name", 
other values could be "email" or "sub" etc...
  },
  "certificates": [
    {
      "Name": "quovadis_ateam-isveng.p12",
      "Content": "MIIQeAIBAzCCEDIGCSqGSIb3DQEHAaCCECMEghAfMIIQGzCCCggGCSqGSIb3DQEHAaCCCfkEggn1MIIJ8TCCCe0GCyqGSIb3PG/HcO5xW0ai3ZkwPTAhMAkGBSsOAwIaBQAEFMyS26dKft/dWRSIVg/SQvDnQmaSBBQTST14Lse+rKA3igKg4Q7gzIB0mAICBAA=",
      "Type": "CERTIFICATE"
    }
  ],
  "authTokens": [
    {
      "type": "SAML2.0",
      "value": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46jAtODUyZS00ODQzNmZlYjZlNjI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPg==",
      "http_header": {
        "key": "Authorization",
        "value": "SAML2.0 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46bAtODUyZS00ODQzNmZlYjZlNjI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPg=="
      }
    }
  ]
}

The decoded SAML assertion:

<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ID="eb2f1943-6239-4856-a4c0-1110e9222b0e" IssueInstant="2021-06-01T21:45:55.366Z" Version="2.0">
<script/>
<saml2:Issuer>quovadis/ateam-isveng</saml2:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#eb2f1943-6239-4856-a4c0-1110e9222b0e">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xsd"/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>M1JVj0c2Gr6KOz0dkfrwlMUCShGPWcX1c891MeSWqfg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue> Xo1eT8mNPTMbhmIUmJjgKn64Qdo2dqZa061l46s1S4/a1sBxRJNYfFqYR/pskG/cLI38QBewqMiq zodR2DUndgZ8CV/i+P3zs7sfCbM6amQRBK1i7/rPaxfo2hRIEfYky/2YKMGuOltpx9E5ke0PgwrB lkwisTVA+RhN37QCHPSlRZFPZcdTlkDrg3fZNdwbzZ4UtOYgTE1abedS6RbZgPVQP2x9P3ZfbqLR URxawxSBbpv8OL5rDQzTH1dymR8UpftfyuvmVRe9uIAUYtY6cHdxqEoi+KaWMBsWrDOu/2IYCtkv Gij52RSFzmei6SGNjhenKo846+3mZYcxWbwZLNWwDENHbDvlh255fULCBhjEvkIgnMBmjd8jZu4w hI86HqMGwrLyDDwEF9yaznBzOSj4YJ+mOzEb73XPz/XlwX1eJFs9odge5SFv6a1zv1bRk+bvVwm7 rasQXCDhkwY8Q4GWbbLJSg3uQW+TRntSLkzTRtwGa/otZfc1Mpw5V6yM8Bx9W7qR6HgF7oH7AKxy rjhGvEyl2cQwKi1zXIOJ8FALCa1GY7wNBAZKV44Dpbsqq6UXkIE/UB+1qCzJBYktOGyrpLw4c03N 5PqyZncb3Gua1m//0/3GCbrPasTXDmT5ruMTl4y5Mw8hDGgDOURDV2+xd/L5LMZrCnaDG3SA8tA= </ds:SignatureValue>
</ds:Signature>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"><QJ9_user_name></saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData NotOnOrAfter="2021-06-01T22:45:55.366Z" Recipient="https://<host>.<domain>:<port>/sap/bc/sec/oauth2/token?sap-client=666"/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2021-06-01T20:45:55.366Z" NotOnOrAfter="2021-06-01T22:45:55.366Z">
<saml2:AudienceRestriction>
<saml2:Audience>QJ9_666</saml2:Audience>
</saml2:AudienceRestriction>
<saml2:OneTimeUse/>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="2021-06-01T21:45:55.366Z">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:x509</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
<saml2:AttributeStatement>
<saml2:Attribute Name="client_id">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">EC_ADM_OAUTH</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="user_uuid">
<saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string"><user_uuid from JWT token></saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>

 

Please pay attention to the following destination definition values as to make sure the saml assertion tags will be populated with the correct values as well.

1. the assertion issuer must be equal to OAuth2.0 Identity Provider name on NW ABAP Side (SAML2 tcode).

If the name of OAuth2.0 Identity Provider is not equal the CN name of the x509 certificate then you will need to use an additional destination service property, namely assertionIssuer to give the OAuth2.0 Identity Provider name.

Otherwise the destination service will automatically use the CN name of the x509 trust in use. Please note as I am using my own trust I had to add this trust to the destination service key store and need to use the these two additional properties, namely

KeyStoreLocation and KeyStorePassword

to refer it to the proper x509 trust.

<saml2:Issuer>quovadis/ateam-isveng</saml2:Issuer>

2. nameIdFormat must be either urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified or urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress

QJ9_user_name is either the business or technical user name

<saml2:NameID Format=”urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified“><QJ9_user_name></saml2:NameID>

3. Recipient (ACS – Assertion Consumer Service endpoint), Please note ?sap-client=666 must be added otherwise the ACS will not be recognized and the assertion rejected as invalid

<saml2:SubjectConfirmationData NotOnOrAfter=”2021-06-01T22:45:55.366Z” Recipient=”https://<host>.<domain>:<port>/sap/bc/sec/oauth2/token?sap-client=666“/>
</saml2:SubjectConfirmation>

4. audience restriction: this is the name of the LocalProvider on QJ9 side, as depicted below:

<saml2:Audience>QJ9_666</saml2:Audience>

5. client_id: this is the name of your OAuth client

<saml2:Attribute Name=”client_id“>
<saml2:AttributeValue xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:type=”xsd:string”>EC_ADM_OAUTH</saml2:AttributeValue>
</saml2:Attribute>

C. Calling into OAuth2.0 client token endpoint

You can use the SAML Assertion you obtained from the destination service find API call with the OAuth2.0 client token endpoint in order to obtain the bearer access token in turn for the subsequent ODATA calls.

The value of assertion is accessible via authTokens[0].value

  "authTokens": [
    {
      "type": "SAML2.0",
      "value": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46jAtODUyZS00ODQzNmZlYjZlNjI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPg==",
      "http_header": {
        "key": "Authorization",
        "value": "SAML2.0 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46bAtODUyZS00ODQzNmZlYjZlNjI8L3NhbWwyOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDI6QXR0cmlidXRlPjwvc2FtbDI6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDI6QXNzZXJ0aW9uPg=="
      }
    }
  ]

    const options = {
      auth: {
      username: credentials.client.id,
      password: credentials.client.secret
      },

    headers: { 
        'Accept': 'application/json',
      }
    };    

    var params = new URLSearchParams();
    params.append('client_id', credentials.client.id);
    if (scope !== '') params.append("scope", scope); 
    params.append('grant_type',  "urn:ietf:params:oauth:grant-type:saml2-bearer");
    params.append("assertion",authTokens[0].value);

  let documents;
  let access_token;
  try {

    const response = await axios.post(tokenUrl + '?sap-client=' + credentials.options.sap_client, params , options);
    documents = JSON.stringify(response.data, null, 2);
    access_token = response.data.access_token;
    console.log(access_token);
    console.log(documents);
    console.log(response.status);

  }
  catch(error) {
      console.log(error.message);
      documents = JSON.stringify(error, null, 2);
  };

As a result we will retrieve the bearer access token as described here.

 

D. Calling into ODATA API

For further details, please goto the relevant section in my sibling blog post.

Conclusion.

As you may have seen using the destination service comes with a certain price. In the end it is yet another tool that you may need to learn first. But certainly, the benefits outweigh the efforts. The less code you have to write and maintain the more time you can spend on the functional aspects of your solution.

Enjoy SAP BTP destination service!

Looking forward to hearing from you. Please post any questions and comments you may have in the add comment section below.

__________

 

Additional resources

ABAP acting as a Resource Server. App2App integration with OAuth2SAML2BearerAssertion flow.

Simple Five Steps to configure API/ODATA services in the SAP S/4HANA On-Premise back-end system to run SAP Intelligent RPA process bots

Implementing Employee Central Payroll (INTERNAL Login required– Authorized for SAP Customers and Partners)

1688545 – OAuth 2.0 Server in AS ABAP Troubleshooting

Good to know:

If your NW ABAP version has built-in support for SAML Bearer Assertion Provider, you might use the SAML Bearer Assertion in the Authorization header of ODATA calls thus bypassing the OAuth2 client brokering. However in this case the recipient of the assertion must reflect the real ACS endpoint (=the API endpoint) and no longer the OAuth client token endpoint!

 

Assigned tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.