Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
Showing results for 
Search instead for 
Did you mean: 
Product and Topic Expert
Product and Topic Expert


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!


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

  • Images/data in this blog post is from SAP internal sandbox, sample data, or demo systems. Any resemblance to real data is purely coincidental.

Employee Central Payroll (ECP)

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

In a nutshell, ECP (PDF link) is an ABAP payroll engine with S/4HANA OP that can be accessed:

  • either over VPN

  • or exposed to the public internet via SAP Gateway

  • or, alternatively, via connectivity proxy coupled to a cloud connector.

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.

  • 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 a S/4HANA Cloud backend).


Putting it all together.

A short 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 Centr...

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 (depicted beside) 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 were a bespoke client application without SFSF tie-in or if your ABAP backend were behind a corporate firewall (that was my case), you can still manage the certificates and outbound destinations manually. And this is how I did it…

  • 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 I 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 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, one 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.

Instead of relying on the SFSF Security Center intrinsic outbound destination facility to generate the saml assertion and request an ECP OAuth client to yield the access bearer token, we shall be generating the saml assertion (sub-steps 5.1a and 5.1b) and then calling into ECP OAuth client to yield the access bearer token (sub-steps 5.2 and 5.3) programmatically! on our own.

Why ? This may be needed 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 deliberately does not have a SFSF EC tie-in, thus SFSF built-in destination service cannot be used either

Please note, 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)!

You may refer to my sibling blog if you want to skip 5.1a and 5.1b and get the generation of saml bearer assertion done and dusted...

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/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI8h/f00fwLMJ/uQ3nYHiwqsElTvKA0h0B5tm\n79w/Z1FBx/vrjqrbKvQEFVQ/zH3YVEsdBPkn4C7iMvumwMECrgbhNTFOAAViJGRqkeRIArXvScbLwq62ViESgOIOU8TdR0n3fachXehZLgRUTa2IGI6zKuVSaXLq\nWBgr0UKz5CLYl4kvZ8ECFbb/I8psoa5LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/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: {
secret: '<EC_ADM_OAUTH system user password>'
auth: {
tokenHost: 'https://<host>.<domain>:<port>/sap/bc/sec',
tokenPath: 'oauth2/token'
options: {
authorizationMethod: 'body'

const credentials_EC_ESS_OAUTH = { // QJ9_666: EC_ESS_OAUTH
client: {
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: {
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-kcapHuuvsU9KHYiuPe0U6p8Xt1rhMr5F4eqkjdRD1***",
"token_type": "Bearer",
"expires_in": "3600",

b. EC_ESS_OAUTH client - self services
"access_token": "-hY-kcapHuuvsd4swh8vxmtVoTf3R187pIQXkV0KX57BQ***",
"token_type": "Bearer",
"expires_in": "3600",

c. QUOVADIS_ECP client - bespoke travel services
"access_token": "-hY-kcapHuuvseHGU63vyPYrQxq7diXlXooux8SFxMQ4v***",
"token_type": "Bearer",
"expires_in": "3600",
"refresh_token": "-hY-kcapHuuvseHGU64PyO5uqYEOUaWH2-XBrfeCi1S5Y***",

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.




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:


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.



Good to know:

  • The SEC_TRACE_ANALYZER defaults to tracing HTTP 401 errors.

  • If your HTTP error or status code is not 401 you must uncheck the logon trace checkbox first. Failure to do so will result in no trace being collected for your workflow!

  • You can also collect HTTP trace for successful execution which you may use later for reference.

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:


Implementation notes:


Using OAuth 2.0 to Integrate Employee Central and Employee Central Payroll

Implementing Employee Central Payroll - Configuring OAuth Identity Provider


SAP SuccessFactors Employee Central Payroll: Integration to S/4 HANA | Public PDF

How to test OAuth 2.0 enabled SAP OData service from POSTMAN Native application?

SAP Gateway community resources

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