We are so used to say “Alexa play spotify” and the likes and no longer realise how much manual work it takes to set up the ubiquitous OAuth2SAML2Bearer Assertion flow with a vanilla SAP ABAP backend system.

This instalment will walk you through this challenge!

However, if you were looking for a more automated approach please have a look at the following blog post where I demonstrate using SAP BTP Destination Service acting as an OAuth IdP provider and bearer access token broker (against an S/4HANA Cloud backend).


  • 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.



The initial task was to set up the SuccessFactor Employee Central integration with the SFSF ECP (Employee Central Payroll) twin via outbound OAuth.

(ECP is an ABAP payroll engine that can be either accessed over VPN or exposed to the public internet.)

Looking at the overview of the required and documented steps (aka Using OAuth 2.0 to Integrate Employee Central and Employee Central Payroll) the task seemed relatively straightforward.

Let’s see…

Good to know:

  • As I did not have access to any ECP instance I did all the setup on a vanilla on-premise ABAP system – SAP S/4HANA 2021. After all an ABAP system is an ABAP system.


Putting it all together.

A quick reminder of the steps to accomplish:

This is an overview of the configuration steps that are needed to set up OAuth 2.0 in Employee Central Payroll (ECP).

Please note.


  • This instalment is focusing on outbound OAuth communications – that means SFSF Employee Central or a 3rd party application is originating ODATA calls towards ECP destinations.
  • SFSF Security Center is the outbound OAuth configuration cockpit, where you can generate the x509 certificates and create and manage destinations towards remote OAuth clients from. But in order to be able to use the SFSF Security Center your ECP destination(s) must be accessible to the SFSF EC instance (via VPN or via public internet).

  • However, if the use case is a bespoke client application without SFSF tie-in or if your ABAP backend is behind a corporate firewall you can still manage the certificates and outbound destinations manually. And this is the focus of this instalment.
  • Otherwise, if you needed to implement inbound OAuth communications with SFSF i.e. towards SFSF you may refer to the following blog post.
Last but not least…


  • The amber coloured banners and sections below offer some configuration guidance that you might want to pay attention to.
  • SA38 / SEC_TRACE_ANALYZER is your friend:)

Step 1. x509 key pair – Creating OAuth X509 Keys

Please note we shall need a private key as well. You may refer to the following blog post for a detailed description of how to generate a .pfx keystore containing a x.509 certificate key pair.

Even if my hands got a little bit rusty with SAP GUI I was able to go through steps 2 – 4 relatively smoothly, as depicted below.

Step 2. SAML2 – Configuring OAuth Identity Provider

Please note that the provider name is the issuer name of the saml assertion.

My recommendation is to use the CN of the x.509 certificate as the provider name.

Step 4. SOAUTH2 – Registering OAuth Client

Please note: the name of an OAuth client cannot be random. It is the name of a system user you must have created beforehand.

In the aftermath, click on the Configuration button to download the OAuth client configuration in json format, for instance:





Please note the saml20_audience above is the audience of the saml assertion!

And finally was ready to test this “ubiquitous” authorisation flow (initially using postman). But all I was getting was an 401 error (=logon error). Please goto troubleshooting section below for detailed explanation.

And while contemplating my bad luck I happened to come across the following community post. The answer provided by Wolfgang Janzen is spot-on!

When I read through it I said to myself – this is it. The missing S_SCOPE object must be the culprit!

Indeed, in order to enable the OAuth client user to act as an OAuth client, you must assign and configure the authorization object S_SCOPE.

And that is done by creating a new role, adding S_SCOPE object to it and assigning the role to the user.

Please refer to the Appendix section for more details.


Step 3. SU01 – Creating Service Users


The required roles, SAP_CLOUD_ADMIN_OAUTH, SAP_CLOUD_ESS_OAUTH, SAP_CLOUD_EMPLOYEE_ESS_PAYSLIP, for the either of ECP OAuth2.0 clients, should already exist on your ABAP system – as described in the note 2900830 – EC-to-ECP: Error handling for OAuth 2.0.

Please add these roles to your ECP system users from the SU01 / Roles tab, namely:

Log in to the Employee Central Payroll system (or a vanilla ABAP system in my case)

  1. Go to transaction SU01 and display the user.
  2. On the “Roles” tab, choose “Display/Change” and add the role SAP_CLOUD_ADMIN_OAUTH for admin services and the role SAP_CLOUD_ESS_OAUTH for self services.
  3. When you are finished, save the data.

Let’s get it done!

After having completed the whole ABAP server side configuration with SAML2 / SU01 / SOAUTH2 / PFCG it is time to create the saml bearer assertion and then call into the ABAP OAuth client to obtain a bearer access token.

The bearer access token will carry all the necessary authorisations to enable a remote and password-less access to ODATA resources.

Step 5. Configuring Outbound OAuth

At this stage we shall deviate from the SFSF/ECP documentation as we shall be generating the saml assertion programmatically! (sub-step 1a and 1b below.)

Why ? This is because:

  • the ABAP server is not exposed to the public internet thus SFSF built-in destination service (the one from the SFSF security center) is not an option.
  • the client application has no SFSF EC tie-in thus SFSF built-in destination service cannot be used either

On the other hand, SAP BTP destination service can help generate the saml bearer assertion in either use case (even if the server or application have no public internet exposure)! Please refer to the sibling blog if this is of interest as well.


1a. Generate SAML bearer assertion.

A saml assertion identifies the resource owner!

The produced saml assertion is both base64- and URL- encoded.

The nodejs code snippet below is provided “as-is”. 

Please pay attention to the nameIdentifierFormat is use.
It must be set to  ‘urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified’
rather than ‘urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified’
// tokenUrl is saml assertion recipient
// audienceUrl is saml assertion audience
// clientId is saml assertion client_id
// userName is saml assertion NameID with the urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified tag
async function generateSAMLBearerAssertion(tokenUrl, audienceUrl, clientId, userName, use_email=false) {

const key = '-----BEGIN PRIVATE KEY-----nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oCnLjT1Im7OtVAVo6mB/wQbEpbOh3LSI8h/f00fwLMJ/uQ3nYHiwqsElTvKA0h0B5tmn79w/Z1FBx/vrjqrbKvQEFVQ/zH3YVEsdBPkn4C7iMvumwMECrgbhNTFOAAViJGRqkeRIArXvScbLwq62ViESgOIOU8TdR0n3fachXehZLgRUTa2IGI6zKuVSaXLqnWBgr0UKz5CLYl4kvZ8ECFbb/I8psoa5LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7JnAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=n-----END PRIVATE KEY-----n';

var options = {
  //cert: fs.readFileSync(__dirname + '/test-auth0.pem'),
  //key: fs.readFileSync(__dirname + '/test-auth0.key'),
  cert: Buffer.from(cert, 'utf-8'),
  key: Buffer.from(key, 'utf-8'),

  issuer: 'quovadis/ateam-isveng',
  lifetimeInSeconds: 3600,
  attributes: {
    'client_id': clientId,
  includeAttributeNameFormat: true, //false,
  // uid: 'b94a5e98-386a-4ce4-b4a2-80a48c2e2222',
  sessionIndex: '_faed468a-15a0-4668-aed6-3d9c478cc8fa',
  authnContextClassRef: 'urn:none', 
  nameIdentifierFormat: use_email === true
            ? 'urn:oasis:names:tc:SAML:2.0:attrname-format:email'  
            : 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
  nameIdentifier: userName,
  recipient: tokenUrl,
  audiences: audienceUrl,
  // signatureAlgorithm: rsa-sha256',
  // digestAlgorithm: 'sha256',
  signatureNamespacePrefix: 'ds',
  //prefix: 'ds',

var unsignedAssertion = saml.createUnsignedAssertion(options);

var signedAssertion =   saml.create(options);
signedAssertion = btoa(signedAssertion);
console.log('btoa-ed signedAssertion: ', signedAssertion);
signedAssertion = encodeURIComponent(signedAssertion);

console.log('unsignedAssertion: ', unsignedAssertion);
console.log('signedAssertion: ', signedAssertion);
return signedAssertion; 


1b. Decode SAML Bearer Assertion into XML format.

Please make a note of the saml assertion Recipient below.
It must have the ?sap-client=<ABAP CLIENT NUMBER> query parameter attached to it.
I recommend you use the token_uri from the downloaded OAuth client configuration
as the Recipient of the saml assertion.
Failure to do so may result in the saml assertion rejection!
<?xml version="1.0"?>
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_Jl7DgoG8CvmWEGWn6BhWhafqdGb6U8eW" IssueInstant="2021-05-25T15:07:46.193Z">
  <ds:Signature xmlns:ds="">
      <ds:CanonicalizationMethod Algorithm=""/>
      <ds:SignatureMethod Algorithm=""/>
      <ds:Reference URI="#_Jl7DgoG8CvmWEGWn6BhWhafqdGb6U8eW">
          <ds:Transform Algorithm=""/>
          <ds:Transform Algorithm=""/>
        <ds:DigestMethod Algorithm=""/>
    <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">QUOVADIS_ECP</saml:NameID>
    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData NotOnOrAfter="2021-05-25T17:07:46.193Z" Recipient="https://<host>.<domain>:<port>/sap/bc/sec/oauth2/token?sap-client=666"/>
  <saml:Conditions NotBefore="2021-05-25T15:07:46.193Z" NotOnOrAfter="2021-05-25T17:07:46.193Z">
  <saml:AuthnStatement AuthnInstant="2021-05-25T15:07:46.193Z" SessionIndex="_faed468a-15a0-4668-aed6-3d9c478cc8fa">
  <saml:AttributeStatement xmlns:xs="" xmlns:xsi="">
    <saml:Attribute Name="client_id" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
      <saml:AttributeValue xsi:type="xs:string">QUOVADIS</saml:AttributeValue>

2. OAuth 2.0 Access Token Request

After receiving a SAML assertion, which identifies the resource owner user,

the OAuth 2.0 client will send an access token request directly at the Gateway

system where the OData service is hosted on to get OAuth 2.0 access token.

The userName is the name of the resource owner.

It must exist and have necessary scopes assigned in its profile.

async function ecp_oauth_access_token(event, userName, use_email=false) {
    const credentials_EC_ADM_OAUTH = { // QJ9_666: EC_ADM_OAUTH
        client: { 
            id: 'EC_ADM_OAUTH',
            secret: '<EC_ADM_OAUTH system user password>'
        auth: {
            tokenHost: 'https://<host>.<domain>:<port>/sap/bc/sec', 
            tokenPath: 'oauth2/token'
        options: {
            authorizationMethod: 'body'
    var scope1 = 'HRSFEC_ECP_INFO_SRV_0001 HRSFEC_INFOTYPE_SRV_0001';

    const credentials_EC_ESS_OAUTH = { // QJ9_666: EC_ESS_OAUTH
        client: { 
            id: 'EC_ESS_OAUTH',
            secret: '<EC_ESS_OAUTH system user password>'
        auth: {
            tokenHost: 'https://<host>.<domain>:<port>/sap/bc/sec',
            tokenPath: 'oauth2/token'
        options: {
            authorizationMethod: 'body'

   const credentials_QUOVADIS_ECP = { // QJ9_666: QUOVADIS_ECP
        client: { 
            id: 'QUOVADIS_ECP',
            secret: '<QUOVADIS_ECP system user password>'
        auth: {
            tokenHost: 'https://<host>.<domain>:<port>/sap/bc/sec',
            tokenPath: 'oauth2/token'
        options: {
            authorizationMethod: 'body'

    let credentials = credentials_EC_ADM_OAUTH;
    let scope = scope1;
    let audienceUrl = 'QJ9_666';

    if (typeof (event.extensions.request.query.oauthclientid) !== 'undefined') {
        oauthclientid = event.extensions.request.query.oauthclientid;
        console.log('oauthclientid: ', oauthclientid);
        if (oauthclientid === 'EC_ESS_OAUTH' || oauthclientid === '2') { 
            credentials = credentials_EC_ESS_OAUTH;
            scope = scope2;
        if (oauthclientid === 'QUOVADIS_ECP' || oauthclientid === '4') { 
            credentials = credentials_QUOVADIS_ECP;
            scope = scope4;
    let tokenUrl= credentials.auth.tokenHost + '/' + credentials.auth.tokenPath; 

    saml_bearer_assertion = await generateSAMLBearerAssertion(
tokenUrl+ '?sap-client=666', 
    console.log('saml_bearer_assertion=', decodeURIComponent(saml_bearer_assertion));
    const options = {
    headers: { 
        'Accept': 'application/json',
        'Authorization': 'Basic ' + btoa( + ':' + credentials.client.secret) 

    var params = new URLSearchParams();
    params.append("scope", scope); 
    params.append('grant_type',  "urn:ietf:params:oauth:grant-type:saml2-bearer");
    params.append("assertion", decodeURIComponent(saml_bearer_assertion));

  let documents;
  try {

    const response = await + '?sap-client=666', params , options);
    documents = JSON.stringify(, null, 2 /*identation */);

  catch(error) {
      documents = JSON.stringify(error, null, 2 /*identation */);
  return documents;

3. OAuth 2.0 Access Token Response

After successful authentication and authorization check for the OAuth client and the resource owner the token endpoint inside the AS ABAP will send an OAuth 2.0 bearer access token back.

Here go examples of successful responses:


a. EC_ADM_OAUTH client – admin services

  "access_token": "-hY-kcapHuuvsU9KHYiuPe0U6p8Xt1rhMr5F4eqkjdRD1xxx",
  "token_type": "Bearer",
  "expires_in": "3600",


b. EC_ESS_OAUTH client – self services

  "access_token": "-hY-kcapHuuvsd4swh8vxmtVoTf3R187pIQXkV0KX57BQxxx",
  "token_type": "Bearer",
  "expires_in": "3600",

c. QUOVADIS_ECP client – bespoke travel services

  "access_token": "-hY-kcapHuuvseHGU63vyPYrQxq7diXlXooux8SFxMQ4vxxx",
  "token_type": "Bearer",
  "expires_in": "3600",
  "refresh_token": "-hY-kcapHuuvseHGU64PyO5uqYEOUaWH2-XBrfeCi1S5Yxxx",


4. OData Service Request and Response

The OAuth client uses the access token in the HTTP bearer authorization header to access the OData service (ZUI_TRAVELPROCESSORMMY).


Request: GET https://<host>.<domain>:<port>/sap/opu/odata/sap/ZUI_TRAVELPROCESSORMMY/?sap-client=666


   let url = 'https://<host>.<domain>:<port>/sap/opu/odata/sap/ZUI_TRAVELPROCESSORMMY' ;

    try {
      const options = {
        headers: {
          'Authorization': 'Bearer ' + access_token,
          'Content-Type': 'application/atomsvc+xml',
      const response = await axios.get(url + '/?sap-client=666', options);
      documents = JSON.stringify(, null, 2);

    catch(error) {
        documents = JSON.stringify(error, null, 2);



  "d": {
    "EntitySets": [




The official SAP help documentation, namely Using OAuth 2.0 to Integrate Employee Central and Employee Central Payroll describes quite accurately all the necessary configuration steps.

Still, the OAuth setup on NW ABAP side can be challenging as of such – as there are many tiny details to pay attention to (as depicted in the amber coloured sections along this blog post).

Last but not least, I hope you have enjoyed reading this blog…Please leave your questions and comments in the add comment section below.


Piotr Tesny




Let me share a hint on how to easily establish a connection with any ABAP backend system using a .sapc formatted connection file.


This is a sample SAP GUI connection file to establish 
a connection to your ABAP backend system

A default gateway port number is 3200
ABAP client number  is 666
FQDN is <host>.<domain>


Good to know:

  • if you are a Mac user you will need a java version of SAP GUI.


The main troubleshooting note is 1688545 – OAuth 2.0 Server in AS ABAP Troubleshooting.

And the transaction SA38 with SEC_TRACE_ANALYZER is your friend.




Here goes the trace for the 401 logon error I encountered initially:

N OAUTH2: Certificate available?:
N OAUTH2: Error! CX_OAUTH2_EXCEPTION thrown, time: 20210523 163432
N OAUTH2: Error Text: No OAuth 2.0 client authentication credentials included or used authentication method is unsupported
N OAUTH2: . Supported authentication methods are specified in the ICF service configuration for the token endp
N OAUTH2: oint in transaction SICF
N OAUTH2: HTTP Status Code: 401
N OAUTH2: error_description: No OAuth 2.0 client authentication credentials included or used authentication method is unsupported.
N OAUTH2: error: invalid_client
N Sun May 23 18:34:32:821 2021


Here goes the rationale of the above 401 error:

To generate access token for client_credentials grant type you must pass the Client ID and Client Secret as a Basic Authentication header (Base64-encoded)
If you try to pass them as form parameters client_id and client_secret you will get 401 error!

Otherwise, all form parameters must be x-www-form-urlencoded.



Using OAuth 2.0 from a Web Application with SAML Bearer Assertion Flow


Configuration Guide for this scenario

To get this scenario running several configuration steps have been performed. Click on the links below to see the step-by-step descriptions for the various components involved. All configuration steps are based on the leave request example.

Configuration Step Tools used
OData Service Enablement
OAuth 2.0 enabling of the approval service, OAuth 2.0 Scope creation and assignment
SAP NetWeaver Gateway Transaction /IWFND/MAINT_SERVICE
Trust Relationship to the Security Token Service (STS)
Configure the Gateway System to trust SAML Bearer assertions issued by the STS
Transaction SAML2
OAuth 2.0 Client Registration
Registration of the OAuth 2.0 client “LEAVEAPP”, which makes calls on behalf of the resource owner user
Transaction SOAUTH2
Resource Owner Authorizations
Assignment of authority object S_SCOPE next to the manager and employee users’ usual permissions
Transaction PFCG



OAuth 2.0 Resource Owner Authorization Configuration


Create OAuth 2.0 client user and add authorization object S_SCOPE

With OAuth 2.0, the access to a resource / service is not done by a user directly, but by an OAuth client. The client logs on to Gateway and sends the user’s access token to the service. Therefore, as a first step we need to create the OAuth 2.0 client in SAP Gateway. This client is not an app, it is a user account of type system that the actual client app will use to log on to SAP Gateway.

To do this run transaction SU01 and create a new system user (user type: system).

With this technical user, the OAuth client app can log on to SAP Gateway. In theory this is enough to allow access to the SAP Gateway service. The client could now send an access token and its client secret to be authorized. As this is not secure enough, the client must not only authenticate itself with User ID and Password or X509 but must also have the authorization to access the service with the given scope and client id.

Within the SAP Backend the authorization object S_SCOPE is used for this purpose. To enable the OAuth client user to act as an OAuth client, you must assign and configure the authorization object S_SCOPE. This is done by creating a new role, adding S_SCOPE object and assigning the role to the user.

Run transaction PFCG and create a new role. For our example we call the role ZOAUTHSERVICE.

The following storyboards describe ZOAUTHSERVICE role configuration steps:


Additional resources:


Using OAuth 2.0 to Integrate Employee Central and Employee Central Payroll


Troubleshooting notes:

1688545 – OAuth 2.0 Server in AS ABAP Troubleshooting

2346664 – Security Trace Analyzer – Improvements

2900830 – EC-to-ECP: Error handling for OAuth 2.0

2259979 – Authentication using SAML 2.0 fails asking to enter the user and password

3009886 – Provided authorization grant is invalid

Generating OAuth X509 Key in SAP SuccessFactors

Generate SAML bearer assertion. Golden selection of help resources.


Randa Khaled

Randa Khaled

Author Since: November 19, 2020

0 0 votes
Article Rating
Randa Khaled
Inline Feedbacks
View all comments