Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

Using Job Scheduler in SAP Cloud Platform [2]: simple OAuth scenario

This tutorial is part of a little series about SAP Job Scheduler

Intro Blog
Previous Blog
Project Files

In the present tutorial we’re going to learn how to create a Node app that exposes a REST endpoint that is protected with OAuth 2.0
And it shows that Jobscheduler is able to trigger that endpoint

It is a simple tutorial that focuses on small portions:
Do the minimum steps in our app, to enforce authentication based on OAuth 2.0
We don’t require authorization
Configure JobScheduler to call the protected application


** Create instances of Jobscheduler service and XSUAA service **
** Create and deploy application **
** Create Job to call that application **
** Background information **


Access to SAP Cloud Platform, Trial or productive landscape
To create the simple application, Node.js is required
For deploying the application, it makes sense to use the Cloud Foundry Command Line Client.

The previous blog of our series is not required, but of course recommended

Create instance of JobScheduler service

First, create an instance of JobScheduler service.
Both service plans, lite or standard, can be used

If you’ve already created an instance, e.g. because you’ve followed my great previous blog,  you can just use it
If you really don’t want to follow the first tutorial, see here to create an instance with name, e.g. jobschedulerinstance

Create instance of xsuaa service

Create simple xsuaa instance:

no params
service plan: broker
name e.g. xsuaa_default

Create Application

In this tutorial, we’re again using a very simple app, which allows us to focus on the essential part:
enabling authentication
With other words: protect our endpoint with OAuth 2.0

Following steps are required

  1. Bind app to XSUAA
  2. Add dependencies to package.json
  3. Add some lines of code
  4. Add trust ACL to app environment

See appendix for full content of all files

1. XSUAA binding

As usual, create manifest.yml file in the project root folder
Add a name for the app, I’ve chosen a name to identify our app: appauthnoscope
(the app requires authentication, but no specific scope/no authorization)

- name: appauthnoscope
  host: appauthnoscope

you might need to change the host name to something unique

Now to add authentication, we need to specify the name of the XSUAA service instance which we created above:

    - xsuaa_default
    - jobschedulerinstance  

2. Dependencies

Additional dependencies required for OAuth authentication, to be added to package.json

"passport": "latest",
"@sap/xssec": "latest",
"@sap/xsenv": "latest"

* We use passport, a standard middleware for express server, used for authentication
* We need the @sap/xssec module provided by SAP. It is used to configure passport with a specific JWT strategy. This is required to validate OAuth tokens issued by SAP XSUAA
* And the @sap/xsenv module to parse the app environment variables. A convenience library, to avoid doing it manually.

Don’t forget to execute npm install

3. Code

The essential line of code is this one:

app.use(passport.authenticate('JWT', { session: false }));

Here we’re adding a middleware to our express server.
This middleware will be called before our endpoint gets invoked
The middleware ensures that authentication is done, otherwise our endpoint doesn’t get invoked

We specify ‘JWT’ as authentication strategy
This means that a JWT token is required
With other words: basic authentication is not possible, you cannot authenticate with your username and password

In addition, the passport middleware itself needs to be configured
It needs an implementation of the authentication strategy
So the following line is required previously:

const jwtStrategy = new JWTStrategy(xsuaaCredentials)

Here, an instance of JWTStrategy (module  @sap/xssec) is passed to passport
This implementation takes care of validating the JWT token, which is sent by the user who calls the endpoint of our app

One last (first) step:
The JWTStrategy has to be configured as well
It has to know how to validate, it needs the “sensitive” information.
I mean, in order to validate a user, it would need the user and password.
As such, in order to validate a JWT token, it needs the OAuth info
So before creating the JWTStrategy, we need to find this OAuth info

When our app is bound to XSUAA, it receives an environment variable for XSUAA (see next chapter)
This variable is a json object and it contains OAuth info (Remember that XSUAA acts as OAuth-Resource-Server)
That json object contains a nested object called “credentials” which contains all info required for following the OAuth flow

We can manually parse the environment variables of our service, but it is more convenient to use @sap/xsenv module
The method call shown below will return the “credentials” object (see below for screenshot), which we store as “xsuaaCredentials”

const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 

That’s all.
Now we can again have a look at all the relevant code, this time in the correct order, from top to bottom:

const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 

const jwtStrategy = new JWTStrategy(xsuaaCredentials)

const app = express();
app.use(passport.authenticate('JWT', { session: false }));

4. Configure ACL

For some reasons (explained below), we need to open our app for foreign users.
It is like adding a “White List” to our app
Remember: ACL stands for “Access Control List”

The XSUAA will look at the environment of our app for a variable containing the ACL
As such, we need to add the following snippet to our manifest.yml

- name: appauthnoscope
  . . .
        {"clientid":"*", "identityzone":"*"}

For the sake of simplicity, we just allow all client IDs and all identity zones.
We’ll enhance it later

Now we’re done with changes to our app, to enforce protection with OAuth 2.0

Small recap:
In manifest, we’ve bound our app to xsuaa + added ACL to env
In code, we’ve added and configured passport with JWT
Of course, we’ve also added the required dependencies to package.json

As you know, you can find the complete content of all files in the appendix section

Now we can deploy our app to the SAP Cloud Platform

Deploy sample app

Yep, deploy it
See here or here for help

Optional: Test the endpoint of our app

After deployment, you can find the app url in the cloud cockpit, in the app “overview” section
After appending the REST endpoint (as defined in our app code), the final URL will look similar like this:

Since we defined the endpoint to be invoked with GET request, we can go ahead and call it, as usual, with our favorite browser.
However, the response is an error: Unauthorized

Second, we can try to use a REST client (e.g. Postman), such that we can pass our user credentials (those which we use when logging into the SAP Cloud Platform)
However, for basic authentication, the response is the same error: Unauthorized with status code 401

At this point you would usually start to panic
BUT: you can safely follow my tutorial – and all will be good 😉

Last attempt: use REST client with authentication type OAuth 2.0
See here for detailed description.

Short description:
In postman, enter the endpoint URL, switch to the “Authorization” tab  and specify OAuth 2.0
Then press “Get New Access Token” and fill the subsequent dialog with information which you get from the app environment (your xsuaa credentials. See section below for more information)

Press “Request Token”, then scroll down and click “Use Token”
Back in Postman screen, you can see that the token is entered and you can press “Send”
The result should be success

Create job in Job Scheduler Dashboard

Last step of our little simple tutorial is to call our endpoint with Job Scheduler

this section is NOT optional.
Users who skip this chapter won’t be rewarded
(users who continue till the very end – won’t get a reward either, I’m afraid)

Creating jobs is described in previous blog

For your convenience and my entertainment (note that I won’t get a reward either…) I’m adding few clickable screenshots

Open Job Scheduler dashboard with little action icon at the right side:

Create Job
Specify action name, e.g.

Create Schedule

After job finishes, check result

The screenshot has proven:
We’ve finished our simple tutorial with a green success message, so it wasn’t totally useless (even without rewards)

What is so amazing about it???

We’ve protected our app with OAuth – but Jobscheduler was still able to successfully invoke the endpoint
We didn’t have to specify clientsecret and similar tedious stuff in Jobscheduler –
Remember how tedious it was in postman? (just in case that you went through that section…)
How is that possible?

Optional: Understanding the authentication mechanism

 If you’re familiar with security and need deeper understanding:
-> this chapter will be useless for you

If you’ve no clue about security and need basic understanding of OAuth:
-> you can follow this blog

If you’d like to waste some time:
-> you can continue reading

About Job Scheduler:
Job Scheduler contacts XSUAA and receives a JWT token which is sent to our app endpoint

About our application:
We’ve created an application and since we’re proud of it, we want everybody to use it
On the other side, since we’re also little selfish, we want only registered users to use it
The SAP Cloud Platform offers the service Authorization & Trust Management (XSUAA) to support such scenarios
We’ve bound our app to that service (xsuaa) and in the implementation code, we’ve added authentication middleware, to ensure (or enforce) the authentication
When binding our app to XSUAA, then the XSUAA provides our app with OAuth information which is required to call our app, following the so-called OAuth flow
This OAuth information, provided as environment variables, contains e.g.

A client ID: it represents a user who wants to call the app (endpoint)
A client secret: that’s like a password
URL: this URL is called with clientid/clientsecret and it responds with a JWT token

The content of environment variables can be viewed
on command line  (cf env <appname>)
or in the cloud cockpit (application details screen)

So, from now on, the flow to call our endpoint is as follows:

1. The user calls the <URL> and uses <clientid> and <clientsecret>
That <URL> has to be composed as follows:
url + /oauth/token
where url is the value of the property url from the credentials section (see above)
2. The response is a JWT token (that’s just a long string with silly characters, e.g. abcxyz)
3. The users calls the endpoint of our app and uses the token in a header:
Authorization: Bearer abcxyz

This is better than using: Authorization = user/password e.g. with a technical user

Our app receives that Bearer token in the “Authorization” header of the req object which is passed into our callback

app.get('/doSomething', function(req, res){  

The authentication is done as follows:

Our app had configured the authentication middleware with the same “credentials” section which we can see in the screenshot above
The bearer token contains exactly the same information, encoded  in the silly string (JWT token)
BTW, this is only the case if the user has properly called the xsuaa server and received a valid JWT token
If this is the case, then the JWT Strategy will allow access to our app endpoint

Now, in our case, the <user> is the Jobscheduler
That makes things a bit more complicated

When Jobscheduler calls our app-endpoint, it sends a JWT token which is valid, but not valid for our app.
The reason is that the JWT token contains a client ID which is not the one we’ve seen in the screenshot above.
JobScheduler is an existing application, it has its own binding to xsuaa, so it receives its own clientid.
We can see it:
It is the one from different VCAP_SERVICE, like shown in screenshot below:

This is rejected in our app code, by our JWT Strategy, because we configured it with the previous xsuaa-credentials, not the jobscheduler-credentials. Which is correct


const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 
const jwtStrategy = new JWTStrategy(xsuaaCredentials)

In my example, the xsuaaCredentials contains this clientid:

As such, the clientid starting with sb-na-cff … is a good one

But the Jobscheduler sends the clientid starting with sb-20db… which is a bad one

As per default, the xssec lib accepts only tokens containing the same client running in the same subaccount.
So the jobscheduler-clientid is rejected.

How can this be fixed?

We need to explicitly allow that bad clientid
That’s done with an environment variable called SAP_JWT_TRUST_ACL
In our sample application we’ve added this variable in the manifest.yml

        {"clientid":"*", "identityzone":"*"}

Here we simply allow all bad clients
We can see this setting in the cloud cockpit:
App details -> User-Provided Variables:

If you were bad and didn’t follow my tutorial, then you can enter the ACL here in the cockpit as well

Third way of adding an environment variable is using the command line
You can use this command:

cf set-env <application-name> SAP_JWT_TRUST_ACL "[{"clientid":"*","identityzone":"*"}]"

This command doesn’t work under windows.
Windows users should try with escape:

cf set-env <application-name> SAP_JWT_TRUST_ACL "[{\"clientid\":\"*\",\"identityzone\":\"*\"}]"

Optional: fine-tuning the ACL

For those of you who feel good with opening your app to all clients and identity zones:
You can skip this chapter and still feel good:
because you save time
And because you’re still quite safe because any client still will be issued by an XSUAA, so not completely coming from wilderness

Nevertheless, if you continue reading, you are free to stop at any time.

So if we want to replace the asterisk, as seen above:

With anything concrete, that at the end would look similar like this:

Then you need to know how to get the client id and the indentity zone

1. client id: You already have it, you take it from the environment variables, jobscheduler section

Note: don’t forget to make sure you’re copying from “jobscheduler” section

2. the identity zone is also taken from the screenshot but there’s a little trick:
Don’t use the identityzone….
The value of the ACL-property “identityzone” has to be the identityzoneID….
See screenshot above

if you’re unsure about which client id is being sent by JobScheduler, you can check it programmatically.
See appendix for code snippet

Now you have 2 possibilities:

Restrict the identity zone or restrict the client id or both (actually 3 possibilities)

1. Identity zone

This means that you trust all JobScheduler instances which are living in the same subaccount like your app.
Doesn’t sound wrong

1. clientid

Only trust one client which has foreign client id.
Can be our jobscheduler.
Can also be another consuming application which is bound to different xsuaa and which you want to add to whitelist
This option is more restricted thus more safe

OK, you can try both options, change the property values of SAP_JWT_TRUST_ACL
e.g. in the cockpit by using the “Edit” button
Or use the command line
Or change the manifest.yml and redeploy the application

Note: in first 2 cases, it is required to restart or restage the application

If any value is invalid, the JobScheduler run will terminate with error “Forbidden”


In this little tutorial we’ve learned how to write a simple Node.js application which is protected with Oath 2.0

We only need to add few lines of code to use libraries which do all the work for us.

Not complicated

And we’ve learned that JobScheduler supports OAuth 2.0 authentication flow without further configuration

Next Steps

The next blog will explain how to enhance the OAuth protection by adding authorization requirement to our application
That will require special handling for JobScheduler

Previous Steps

Previous blog and Intro blog

Previous blog and Intro blog

SAP Help Portal: Job Scheduler documentation
SAP Help Portal Job Scheduler: Security chapter
SAP Help Portal XSUAA documentation

And rfc:

And rfc:
A tool to view token content:

Passport module and documentation

Advertising my own blogs
How to call OAuth protected endpoint with REST client
Some basic simple introduction to OAuth protection and OAuth grant type
Sample to call protected API from Node.js application, and one more sample in cloud
Using command line client for cloud
About reading JWT token and more

Appendix 1: All Project Files

In this appendix you can find all files required to run the described sample application.
For your convenience, see here again the folder structure:

Find below the content of all 3 requried files


- name: appauthnoscope
  host: appauthnoscope
  path: myapp
  memory: 128M
    - nodejs_buildpack
    - xsuaa_default
    - jobschedulerinstance
        {"clientid":"*", "identityzone":"*"}


  "main": "server.js",
  "dependencies": {
    "@sap/xsenv": "latest",
    "@sap/xssec": "latest",
    "express": "^4.16.3",
    "passport": "^0.4.1"


const express = require('express');
const passport = require('passport');
const xsenv = require('@sap/xsenv');
const JWTStrategy = require('@sap/xssec').JWTStrategy;

//configure passport
const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 
const jwtStrategy = new JWTStrategy(xsuaaCredentials)

// configure express server with authentication middleware
const app = express();
app.use(passport.authenticate('JWT', { session: false }));

// app endpoint which will be called by jobscheduler
app.get('/doSomething', function(req, res){   
   res.send('App endpoing finished doing something');

const port = process.env.PORT || 3000;
app.listen(port, function(){})

Appendix 2: Retrieve client id programmatically

We want to retrieve the JWT token which is sent to our app in the authorization header.
The token contains sensitive information about client id etc
In case of our sample app, we need to add an own middleware, which has to be invoked before passport-middleware.
Because if passport would reject the incoming call, we wouldn’t be invoked, thus no chance to read the JWT token

// Middleware to read JWT sent by JobScheduler
function jwtLogger(req, res, next) {
    console.log('===> [JWT-LOGGER]  decoding auth header' )
    let authHeader = req.headers.authorization;
    if (authHeader){
       var theJwtToken = authHeader.substring(7);
          let jwtBase64Encoded = theJwtToken.split('.')[1];
             let jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
             let jwtDecodedJson = JSON.parse(jwtDecoded);
             console.log('===> [JWT-LOGGER]: JWT contains scopes: ' + jwtDecodedJson.scope);
             console.log('===> [JWT-LOGGER]: JWT contains client_id sent by Jobscheduler: ' + jwtDecodedJson.client_id);
    .use(passport.authenticate('JWT', { session: false }));
// . . . etc


Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Sampath Ramanujam
      Sampath Ramanujam

      Thanks for the information. Do you have the setup instructions suppose i have approuter thats responsible for the service to generate authtoken for the application. How do i configure job scheduler to work with approuter

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Sampath Ramanujam , I've created a Blog to answer your question regarding approuter
      Have fun 😉

      Author's profile photo Jason Powell
      Jason Powell

      Is it possible to obtain the identity zone from the deployer, e.g. with a placeholder?  We'll be deploying our application into multiple sub-accounts / identify zones.

      Author's profile photo Cedric Heisel
      Cedric Heisel

      Hi Carlos Roggan ,

      as I read in your Blog post about the outdated JWT ACL environment parameter,

      I'm wondering how I can extend the scope of the job scheduler to add the clientID of the job-scheduler to the audience to set trust to my job application.

      Speaking for the community ... can you do us catlovers a favor and update this?


      I really appreciate your job ... thank you!!!

      Author's profile photo Anuj Kumar
      Anuj Kumar

      Hi Carlos Roggan ,  your blog is very informative. Thank you so much for sharing this great information. Could you provide your inputs on issues here I am facing given below?

      1: Getting error -401  while scheduling the job. Here I have not setup authorization just setup authentication(Using Job Scheduler in SAP Cloud Platform [2]: simple OAuth scenario | SAP Blogs)

      2:  Also I tried to create xsuaa instance given on next tutorial(Setup authorization)  with "grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"] then getting error given below.

      Service broker error: Service broker xsuaa failed with: Error creating application null (Error parsing xs-security.json data: Inconsistent xs-security.json: grant-as-authority-to-apps or granted-apps not supported on null)


        "xsappname" : "jobschedulexsuaa",
        "tenant-mode" : "dedicated",
        "scopes": [{
            "name": "$XSAPPNAME.scopeformyapp",
            "description": "Users of my great app need this special role",
            "grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"]



      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Anuj Kumar ,

      Thanks for your nice feedback.
      The error message sounds like the "jobschedulerinstance" could not be resolved.
      Reason could be a typo?
      Or maybe you didn't follow the necessary order: the jobscheduler instance must be created before you create the xsuaa.
      Remember: Create JOBS first...;-)


      Author's profile photo Anuj Kumar
      Anuj Kumar

      Hi Carlos Roggan , Thank you for your response.

      I used same name in both place(xs-security.json& manifest.yml). I think Shreyas had similar issue mentioned here Using Job Scheduler in SAP Cloud Platform [3]: enable OAuth Authorization | SAP Blogs. Also he had 401 error same as mine.


      - name: jobappsimpleauth
        path: app
        memory: 128M
        random-route: true
          - nodejs_buildpack
          - xsuaa-service
          - jobschedulerinstance
          SAP_JWT_TRUST_ACL: >
              {"clientid":"*", "identityzone":"*"}
      Author's profile photo Anuj Kumar
      Anuj Kumar

      Hi Carlos Roggan ,   this issue has been resolved when I moved jobscheduler service from plan lite to standard ). Thanks


      Author's profile photo d rupireddy
      d rupireddy

      Hi Anuj,

      Is this Oath Authorization can't work in "lite" plan? as i understand

      Author's profile photo Raheel Ahmad
      Raheel Ahmad

      Hi @Carlos Roggan ,

      First of all, great blog series.. Thanks 🙂

      Now, the second thing is the issue I need your help with , I am getting `401 UnAuthorised` error when running schedules, but it works okay when using xsuaa client id and secret.

      I have tried re-creating the services in mentioned order and also tried using the productive environment with standard plan for jobscheduler service since someone told me the lite version is a bit different from the standard version.So, Can you kindly have a look and let me know if I am doing something wrong?

      Here the app logs:

         2022-04-11T13:41:27.92+0200 [APP/PROC/WEB/0] OUT ===> [JWT-LOGGER]  decoding auth header
         2022-04-11T13:41:27.92+0200 [APP/PROC/WEB/0] OUT ===> [JWT-LOGGER]: JWT contains scopes: uaa.resource
         2022-04-11T13:41:27.92+0200 [APP/PROC/WEB/0] OUT ===> [JWT-LOGGER]: JWT contains client_id sent by Jobscheduler: sb-ac3c67bf-f0bb-4c87-bfca-e9c8f18fcb0f!b5618|sap-jobscheduler!b21
         2022-04-11T13:41:27.97+0200 [RTR/49] OUT - [2022-04-11T11:41:27.914502248Z] "GET /dosomething HTTP/1.1" 401 0 12 "-" "-" "" "" x_forwarded_for:"," x_forwarded_proto:"https" vcap_request_id:"32f18eec-1076-4e77-4f61-7a8bd949a689" response_time:0.058273 gorouter_time:0.000050 app_id:"6389de1f-1c1f-4226-a3fd-165bb6230374" app_index:"0" instance_id:"6b09314a-b752-43bc-6d82-d802" x_cf_routererror:"-" x_correlationid:"-" tenantid:"-" sap_passport:"-" x_scp_request_id:"4c5c91a2-dfb0-4aa1-bcc5-8e3b31324d0b-625413E7-11545BF" x_cf_app_instance:"-" x_forwarded_host:"-" x_custom_host:"-" x_ssl_client:"-" x_ssl_client_session_id:"-" x_ssl_client_verify:"-" x_ssl_client_subject_dn:"-" x_ssl_client_subject_cn:"-" x_ssl_client_issuer_dn:"-" x_ssl_client_notbefore:"-" x_ssl_client_notafter:"-" traceparent:"00-d388fb4f55158cc282e8ae6ffd947be6-50a41c9e7cc2e179-01" x_b3_traceid:"601113e7baf061fe" x_b3_spanid:"601113e7baf061fe" x_b3_parentspanid:"-" b3:"601113e7baf061fe-601113e7baf061fe"



      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hello Raheel Ahmad ,

      As far as I can see, the log shows that Jobscheduler sends its own client_id.
      Also, it sends only the default scope (uaa.resource) which is added by Cloud Foundry in case of client-credentials.
      Your XSUAA instance has different client_id.
      As such, it rejects the request of Jobscheduler.
      One question: have you defined a scope in your application, and have you granted it to Jobscheduler?
      This is required.
      During the grant mechanism, the client_id of YOUR own xsuaa will be added to the "aud" claim.
      As such, when Jobscheduler sends the JWT token along with its request, the token will contain YOUR client_id and that will be accepted by your xsuaa.
      Please refer to my other blog posts, I described it in detail there.

      BTW, one more info:
      Whenever you do a change related to xsuaa or xs-security, or JWT token, you will need to wait 12 hours before the change gets effective. Reason is that Jobscheduler caches the token during 12 hours, to improve performance:
      Hehe, if your manager doesn't like to see you "waiting" 12 hours, you can try to delete and recreate a  job, as a workaround. 😉

      And thanks for your feedback 😉

      Author's profile photo Raheel Ahmad
      Raheel Ahmad

      Hi @Carlos Roggan,

      Thanks for your answer.

      So, I investigated this further with help from my team and the issue turned out to be with "xssec" package version. So, now the latest versions take into account the 'aud' in the token as well which wasn't the case in older versions.

      so, if someone does exactly what is explained in this blog then it will work properly if you use an older version of @sap/xssec and not the latest one. In my case, I tried version 2.2.5 and it worked.

      Also, your suggestion regarding adding scopes in application for jobscheduler should also work but I haven't tried that yet on my side.

      and yes, I have already hit the 12 hours milestone once but now too many people know about that to correct you. 😉


      Thanks and i appreciate having this blog series.




      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Thank you for the feedback, Raheel Ahmad , and thanks for sharing your finding with the community!
      Carlos 😉

      Author's profile photo Mariam Id-Manssour
      Mariam Id-Manssour

      Hi Carlos,


      Thank you for your detailed Blog Post  and all the explanation in every step .

      For others who work with SAP Job Scheduling service with cloud application programing (CAP) and XSUAA  there is another great blog post of Vinh Phat Tu  about this use case which was very useful for me as well.


      Best regards,