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

Using Job Scheduler in SAP Cloud Platform [3]: enable OAuth Authorization

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

Quicklinks:
Intro Blog
Tutorial 1
Tutorial 2
Project Files

In the previous tutorial, we created a Node.js application which was protected with OAuth 2.0,
and we used JobScheduler to call our app
That app required authentication, but not authorization

In this tutorial we‘re going to add authorization requirement to the app
It is again a simple tutorial, there are only a few steps which require special attention

Note that it requires productive account, it cannot be executed in Trial account

Overview

** Create instances of JobScheduler and xsuaa (security settings: xs-security) **
** Create Node.js application **
** Optional: Test with human user **
** Configure JobScheduler **
** Appendix 1: assign role to user **
** Appendix 2: list of useful CLI commands**
** Appendix 3: Logging the JWT content**
** Appendix 4: more apps with more scopes **
** Appendix 5: all project files **

Prerequisites

  • Access to productive account of SAP Cloud Platform
  • The previous blog is not a prerequisite, as we’re going to re-create the app from scratch
    However, we’re not going to re-explain the same stuff
  • So, let’s make it a prerequisite: the previous blog

Create instance of Job Scheduler service

The instance of Job Scheduler service needs to be created first (before xsuaa) and with below given parameter.
Reason:
The Job Scheduler instance must exist before proceeding with next step.
Reason:
The instance name will be referenced by xsuaa security descriptor

Below parameter is required, because we’re going to configure Job Scheduler to call an OAuth-protected REST endpoint of our app.
This parameter is not required, when JobScheduler is used to call Cloud Foundry Tasks

See here to create the service instance using the cockpit

Details:

Service Plan: Standard
Parameter:

{
   "enable-xsuaa-support": true 
}

Instance Name: jobschedulerinstance

Command for creation on command line:

cf create-service jobscheduler standard jobschedulerinstance -c "{\"enable-xsuaa-support\":true}"

Note:
Windows users need to escape the quotation marks as shown above

Takeaway: Create JOBs first

Create instance of XSUAA service

In this tutorial, we want to add authorization requirement to our application
This means, that users who want to call our REST endpoint, need to be authenticated AND must have a special role
This role is defined by us, the app developers. We require it
Why?
Because our endpoint might be sensitive, so we want to ensure that e.g. only administrators can invoke it

How to define a role?
When creating an instance of XSUAA, we pass JSON parameters which contain the scope (role) that we require
We can specify any arbitrary name of our choice
Let’s give it a stupid name: scopeformyapp
To make that name a bit more unique, we concatenate our chosen name to the property xsappname  which has to be unique anyways.
This is a best practice and we can use a variable to avoid typos:

{
  "xsappname" : "xsappwithscopeandgrant",
  "scopes": [{
      "name": "$XSAPPNAME.scopeformyapp",

This means that at runtime, our role name will be resolved to something like
xsappwithscopeandgrant!t22273.scopeformyapp

The value of property xsappname can be viewed:
-> in the env of your app,
-> VCAP for xsuaa,
-> credentials section

Note:
It makes sense to create an endpoint in a productive application which is only used by JobScheduler for recurring tasks, like clean-up database.
So it would make sense to name the scope accordingly.
Furthermore, if the scope is not referenced by a role-template, then it cannot be assigned to a human user, then it cannot be found in the list of available roles (see appendix)

OK, we’ve learned to define roles when creating an instance of xsuaa.
That role must be assigned to any user who wants to call an application, which is bound to the xsuaa instance which knows about that role

Problem:
JobScheduler is not a user, so we cannot assign a role to it

Solution:
The SAP Cloud Platform provides a mechanism to assign a role to an application:
grant-as-authority-to-apps

We only have to add the following line to our scope-definition:

"grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"]

Note:
Make sure to adapt to the name of your instance name of JobScheduler service created above

Note:
Now you can see the reason why JobScheduler instance has to be created first
(remember: create JOBs first)

Finally, the JSON parameters which are passed to the instance of XSUAA look as follows:

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

The above snippet can be copy&pasted during instance creation in the cockpit
However, it makes sense to store these parameters in a file.
Such file is usually called xs-security.json, but the name can be any arbitrary name
Also, that file is not required at runtime, so it doesn’t need to be deployed along with the app

Details for xsuaa instance creation:
Service Plan: application or broker
Parameters: as given above
Instance Name: xsuaawithscopeandgrant

Command line users can use the following command /assuming that the above parameters are stored in a file with name xs-security.json in the same directory)

cf create-service xsuaa broker xsuaawithscopeandgrant -c xs-security.json

Note for user who already have an app and xsuaa

In our tutorial, we’re going to create a new application
However, if you already have an app which is bound to xsuaa:
You can just update the existing xsuaa instance to add the above JSON parameters (e.g. the grant for JobScheduler)
Command for update looks as follows:

cf update-service xsuaawithscopeandgrant -c xs-security.json

However, after update-service, make sure to re-bind the service to your app
But always:

FIRST bind XSUAA to app
THEN bind Jobscheduler to your app

Background:
When JobScheduler is bound to our app, the JobScheduler reads the grant-as-authority-to-apps
Thus: our app MUST be bound to xsuaa (with grant) BEFORE binding to JobScheduler

Otherwise the invocation of our endpoint would fail, because JobScheduler wouldn’t have the required scope

Note:
You can always unbind and bind JobScheduler instance after having updated the xsuaa service (after bind: restage or restart the app)

These commands are useful:

cf unbind-service <yourappname> jobschedulerinstance
cf bind-service <yourappname> jobschedulerinstance
cf restage <yourappname>

Takeaway: Create JOBs first
Takeaway: Bind xsUSA first again

Create Application

We’re going to reuse the application of previous blog
Here, we’re only explaining the differences

1. manifest.yml

The first step for enhancing our app with authorization was done in previous step, when creating the instance of xsuaa
In manifest.yml file, we just bind our app to the service instances and define the ACL

applications:
- name: appauthandscope
  . . .
  services:
    - xsuaawithscopeandgrant
    - jobschedulerinstance

Note:
The services-section of above snippet shows that we bind the app to xsuaa first.
This order of binding will be kept during deployment. At least in most cases. However, there’s no guarantee
So in case of trouble, remember:
Bind xsUSA first again
And: after binding xsuaa, don’t forget to unbind and bind JobScheduler and restage

2. package.json

<No changes>

Don’t forget to run npm install

3. Code

In the previous app we’ve enforced the authentication with passport and the JWT strategy of xssec library
Now we only need to enforce the authorization
We’re going to do it manually

We have to understand:
Whenever a user authenticates against XSUAA, he receives a JWT token
(Remember: xsuaa acts as “Authorization Server” in the OAuth flow)
That token contains info about clientid etc and also info about the scopes (roles) which the user has

So our task seems to be clear:
When our endpoint is invoked,
-> we have to extract the scopes out of the token
-> and check if our required scope is available.

And we have to react accordingly:
If the scope $XSAPPNAME.scopeformyapp is not contained in the JWT token, we have to fail with corresponding HTTP status code

For the application developer, it doesn’t make a difference if the user is a human, or if it is the JobScheduler:
If the instance creation and binding was done properly, the JobScheduler will send the required scope in the JWT token

And one more good news:
There’s a helper method available which does the validation of the token
We don’t really need to manually decode the token to get the list of available scopes to check if our required scope can be found
We only need to use the helper method and pass our required scope name to it

But first we have to figure out, which is the correct name of the scope (role) which a user needs to have to call our endpoint successfully (remember that the full xsappname is generated during deployment)
In our xs-security.json we defined it as follows

xsuaaCredentials.xsappname
  "scopes": [{
      "name": "$XSAPPNAME.scopeformyapp",

We’ve learned that during deployment it will be generated and look somehow like this:
xsappwithscopeandgrant!t22273.scopeformyapp

So we cannot hard-code the required name.
We have to obtain the $XSAPPNAME from our app environment
It is easy
We already have parsed the environment in the previous tutorial:

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

Now we can access the credentials to get the value of the property “xsappname”

xsuaaCredentials.xsappname

We could store it in a constant.
But no, in this simple tutorial we make it the simple way…
See here how the implementation of our endpoint looks like:

app.get('/doSomething', function(req, res){      
   const MY_SCOPE = xsuaaCredentials.xsappname + '.scopeformyapp'
   if(req.authInfo.checkScope(MY_SCOPE)){
      res.send('The endpoint was properly called, the required scope has been found in JWT token. Finished doing something successfully');
   }else{
      return res.status(403).json({
         error: 'Unauthorized',
         message: 'The endpoint was called by user who does not have the required scope: <scopeformyapp> ',
     });
   }
});

The promised helper is this one:

req.authInfo.checkScope('theScope'))

Actually, there are 2 helpers.

First helper:

req.authInfo

This is an object that is filled by the framework, it is a convenience object
We don’t need to know how to access the “Authorization” header
And best: it contains info extracted from parsed JWT token

BTW, see Appendix for a simple example for manually extracting info from JWT token

Second helper:

authInfo.checkScope('theScope')

The authInfo object provides not only properties, but also a helper method
It doesn’t do sophisticated stuff, but is handy
It checks if the JWT token contains the scope
If the check fails, then our app responds with a proper status code
As of specification, the code 403 is the right one for our error (required scope not available)

OK, that’s all

As I promised, also in this tutorial, there’s not much work to do and the work is simple:

1. Compose the scope name which our app requires
2. Validate the scope of incoming call

Deploy

<empty chapter>

Optional: Test the endpoint of our app

In the previous tutorial, I gave brief description about how to use REST client for OAuth flow
You can repeat the steps for current scenario
However: -> it will fail

Why?
As a human user, I proceed as follows:
– Open the cockpit, go to the app details page, open the Environment Variables and view the VCAP_SERVICES variable for the xsuaa-binding of my app
– With the help of postman, ask the authentication endpoint of XSUAA (the Authorization server in terms of OAuth) to provide a JWT token for my user
– The identity provider is contacted to verify my user.
– And here comes the weak point:
my user has roles assigned. But obviously not the silly role $XSAPPNAME.scopeformyapp required by the simple app

How to fix it?
-> See Appendix

Create Job

Create Job with endpoint URL like this

https://appauthandscope.cfapps.eu10.hana.ondemand.com/doSomething

And result like this:

Summary

Here comes a summary of 2 blogs:

We’ve learned how to write a node.js app which is protected by OAuth
We’ve learned a bit about the protection mechanism:
Using passport and xssec JWT Strategy
We’ve learned how to define a scope
We’ve learned how to enforce a scope

I almost forgot that this tutorial series is about Jobscheduler:
We almost don’t need to do anything to make JobScheduler call our protected app
Add jobscheduler to ACL
Assign (grant) the role to JobScheduler

Remember:
The order of creation and order of binding is important
Never forget the mnemonics for correct Order :

Takeaway: Create JOBs first
Takeaway: Bind xsUSA first again

Maybe a diagram helps to remember?

Links

SAP Help Portal: Security in Cloud Foundry
SAP Help Portal: xs-security.json docu
SAP Help Portal: xs-security.json reference
SAP Help Portal: little docu about grant

HTTP status codes: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

Security Glossary.

More links: see previous blog

Series:
0 Intro
1 Simple
2 Authentication
3 This blog
4 App Router

Appendix 1: assign role to user

My apologies, I didn’t expect that you would be interested in calling that endpoint with REST client
That’s why above I described a rather simple security descriptor.
It contained only the parameters needed by JobScheduler
Now, you want to call your silly app endpoint with your (smart) human user
OK.
I can describe that
sigh

We have to enhance the security descriptor.

Copy the following content into xs-security.json file or create a second file with some silly name of your choice, e.g. xs-humanSecurity.json

This time, the parameters contain an additional role-template:

{
  "xsappname" : "xsappwithscopeandgrant",
  "tenant-mode" : "dedicated",
  "scopes": [{
      "name": "$XSAPPNAME.scopeformyapp",
      "description": "Users of my great app need this special role",
      "grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"]
  }],
  "role-templates": [ { 
      "name"                : "RoleTemplateForMyGreatApp", 
      "description"         : "Users of my great app need this special role", 
      "default-role-name"   : "My App My Role",
      "scope-references"    : ["$XSAPPNAME.scopeformyapp"]
  }]
}

See SAP Help Portal for more information about params

Now update the xsuaa service instance with following command

cf update-service xsuaawithscopeandgrant -c xs-humanSecurity.json

Now you have to ask the admin (probably yourself) to assign that silly role to your user
2 steps are required:

1. Create Role Collection containing our Role

In cockpit,
-> go to subaccount
-> expand Security Menu on the left pane
-> click Role Collections

Create Role Collection with any name of your choice, e.g. rc_app_with_scope
Click hyperlink of new Role Collection
Click button “Add Role”, choose our xsappname (xsappwithscopeandgrant) and see the role template and default role as defined in the security descriptor above

Press Save

2. Assign Role Collection to user

In cockpit
-> subaccount
-> Security menu
-> click Trust Configuration

Click on current identity provider (e.g. SAP ID Service)
In “Trust Configuration” screen, enter your E-Mail Address
Then press “Show Assignments” to view the Role Collections assigned to your user
Obviously, the new Role Collection is not listed
Press button “Assign Role Collection” and select the new Role Collection

Press Assign
Now your user can be proud to be a bearer of the great new role
And now you can proceed with chapter Optional: Test the endpoint of our app
And I promise: it will work fine now!

Appendix 2: useful commands

For your convenience, I’m listing all commands for Cloud Foundry Command Line Client, ready to copy&paste for this tutorial

Creating the services:

cf create-service jobscheduler standard jobschedulerinstance -c "{\"enable-xsuaa-support\":true}"
cf create-service xsuaa broker xsuaawithscopeandgrant -c xs-security.json

Update:

cf update-service xsuaawithscopeandgrant -c xs-security.json

Re-binding xsuaa:

cf unbind-service appauthandscope xsuaawithscopeandgrant
cf bind-service appauthandscope xsuaawithscopeandgrant
cf restage appauthandscope

Re-binding jobscheduler

cf unbind-service appauthandscope jobschedulerinstance
cf bind-service appauthandscope jobschedulerinstance
cf restage appauthandscope

Appendix 3: app with JWT logger

If you’re interested in adding logs to your app, to view the JWT token which is sent, and to verify the scopes with your eyes, use the following code snippet in your node app

One hint: the property user_name will be filled only when you call the endpoint with your user and REST client. JobScheduler doesn’t have a user_name

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)
passport.use(jwtStrategy);

// scope name copied from xs-security.json
const SCOPE_NAME = 'scopeformyapp'
const MY_SCOPE = xsuaaCredentials.xsappname + '.' + SCOPE_NAME;

// configure express server with authentication middleware
const app = express();

// Middleware to read JWT sent by JobScheduler
function jwtLogger(req, res, next) {
   console.log('===> [MIDDLEWARE]  decoding auth header' )
   let authHeader = req.headers.authorization;
   if (authHeader){
      var theJwtToken = authHeader.substring(7);
      if(theJwtToken){
         console.log('===> [MIDDLEWARE] the received JWT token: ' + theJwtToken )
         let jwtBase64Encoded = theJwtToken.split('.')[1];
         if(jwtBase64Encoded){
            let jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
            let jwtDecodedJson = JSON.parse(jwtDecoded);
            //console.log('User: ' + jwtDecodedJson.user_name);
            console.log('===> [MIDDLEWARE]: JWT: scopes: ' + jwtDecodedJson.scope);
            console.log('===> [MIDDLEWARE]: JWT: client_id: ' + jwtDecodedJson.client_id);
            console.log('===> [MIDDLEWARE]: JWT: user: ' + jwtDecodedJson.user_name);
         }
      }
   }
   next()
}
app.use(jwtLogger)

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

// app endpoint with authorization check
app.get('/doSomething', function(req, res){      
   if(req.authInfo.checkScope(MY_SCOPE)){
      res.send('The endpoint was properly called, the required scope has been found in JWT token. Finished doing something successfully');
   }else{
      return res.status(403).json({
         error: 'Unauthorized',
         message: 'The endpoint was called by user who does not have the required scope: <scopeformyapp> ',
     });
   }
});

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

Note:
Better not write scopes to logs when running in productive mode

Appendix 4: more apps with more scopes

Curious about how JobScheduler behaves when there are many apps bound to it?
ONE instance of JobScheduler and MANY apps bound to it.
As a consequence, MANY Jobs created for many apps

You can try, little experiment:

Create a second xsuaa, add multiple scopes in json params
Then create a second (silly) app, bind it to this xsuaa and to Jobscheduler.
Deploy it
Then run job of first app again and check the log output (previous Appendix) for the scopes.

You’ll see: the JWT token which is sent by JobScheduler to first app, contains all scopes: the one of first app and those of second app

You can use this xs-security.json:

{
  "xsappname" : "xsappwithscopeandgrant2",
  "tenant-mode" : "dedicated",
  "scopes": [
    {
      "name": "$XSAPPNAME.scopeformyapp2",
      "description": "scope2",
      "grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"]
    },
    {
      "name": "$XSAPPNAME.scopeformyapp3",
      "description": "scope3",
      "grant-as-authority-to-apps": ["$XSSERVICENAME(jobschedulerinstance)"]
    }
  ]
}

Appendix 5: All Project Files

manifest.yml

---
applications:
- name: appauthandscope
  path: app
  memory: 128M
  buildpacks:
    - nodejs_buildpack
  services:
    - xsuaawithscopeandgrant
    - jobschedulerinstance

xs-security.json

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

package.json

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

server.js

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)
passport.use(jwtStrategy);

// configure express server with authentication middleware
const app = express();

// Middleware to read JWT sent by JobScheduler
function jwtLogger(req, res, next) {
   console.log('===> [MIDDLEWARE]  decoding auth header' )
   let authHeader = req.headers.authorization;
   if (authHeader){
      var theJwtToken = authHeader.substring(7);
      if(theJwtToken){
         console.log('===> [MIDDLEWARE] the received JWT token: ' + theJwtToken )
         let jwtBase64Encoded = theJwtToken.split('.')[1];
         if(jwtBase64Encoded){
            let jwtDecoded = Buffer.from(jwtBase64Encoded, 'base64').toString('ascii');
            let jwtDecodedJson = JSON.parse(jwtDecoded);
            console.log('===> [MIDDLEWARE]: JWT: scopes: ' + jwtDecodedJson.scope);
            console.log('===> [MIDDLEWARE]: JWT: client_id: ' + jwtDecodedJson.client_id);
            console.log('===> [MIDDLEWARE]: JWT: user: ' + jwtDecodedJson.user_name);
         }
      }
   }
   next()
}
app.use(jwtLogger)

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

// app endpoint with authorization check
app.get('/doSomething', function(req, res){      
   const MY_SCOPE = xsuaaCredentials.xsappname + '.scopeformyapp'
   if(req.authInfo.checkScope(MY_SCOPE)){
      res.send('The endpoint was properly called, the required scope has been found in JWT token. Finished doing something successfully');
   }else{
      return res.status(403).json({
         error: 'Unauthorized',
         message: 'The endpoint was called by user who does not have the required scope: <scopeformyapp> ',
     });
   }
});

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

 

Assigned Tags

      33 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jens Baumgart
      Jens Baumgart

      Hi Carlos, this is very helpful information!

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

      Hey Jens, thanks very much!

      Author's profile photo Atul Shrivastava
      Atul Shrivastava

      Thanks Carlos, for a great blog. Is it possible to pass Content-Type with the URL I am trying to call from Job Scheduler. I am trying a post verb and have data to post with correct content-type...

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

      Hello Atul Shrivastava ,
      Setting custom headers is (currently) not supported.
      However, in your case, the content-type, you can ignore, as long as it is normal json payload.
      Hope that suits your needs
      Cheers, Carlos

      Author's profile photo Atul Shrivastava
      Atul Shrivastava

      Ok, thank you for your quick revert, I am actually trying to schedule an SCP API doing a post operation.  It works fine when run in API Test mode or even from postman. When it is run from Job Scheduler it always return 500 Internal Server Error so I was assuming maybe it is because of content-type or some header which is going wrong. Does this Job Scheduler makes some entries in logs which I can check?

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

      Hi Atul Shrivastava, hum, that's interesting, let's continue troubleshooting via personal messaging

      Author's profile photo Atul Shrivastava
      Atul Shrivastava

      Sure, pinged you on personal window.

      Author's profile photo Shreyasi Chakraborty
      Shreyasi Chakraborty

      Hi  Carlos Roggan,

      Your blog is very informative and helpful, yet i couldn’t succeed.

      I am facing one issue  –

      My xsuaa service plan is space, jobschedular plan is default and it is in SAP HANA XS Advanced.

      When I try to update my xsuaa service instance with xs-security.json using cli, I get the following error:

      Error%20updating%20xsuaa%20service%20using%20xs-security.json

       

      Error updating xsuaa service using xs-security.json

       

       

       

      But when I remove $XSSERVICENAME from xs-security.json as shown below –

      {
          "grant-as-authority-to-apps": ["XSA_SAC_JOBSCH"] 
      }
      Then I am able to update my xsuaa service using xs-security.json in cli.
      And after following all other steps that you have mentioned, I still get 401 unauthorized error in my schedular.
      Schedular
      I dont understand where’s the issue?
      Note – You have written blog with respect to SAP Cloud Platform whereas I am trying it in SAP HANA XS Advanced.
      Author's profile photo Thanu Kumaraswamy
      Thanu Kumaraswamy

      Hi @Shreyasi Chakraborty

      did you manage to solve the issue? if so can you please share how?

      Regards,

      Thanu

       

      Author's profile photo Thanu Kumaraswamy
      Thanu Kumaraswamy

      Hi Carlos Roggan,

      Very nice series of blogs, helped a lot with understanding the Scheduler service. However, I am stuck in this series, while creating the job scheduler instance ( both via command line and online ) the following error is thrown.

      Service broker error: Service broker xsuaa failed with: org.springframework.cloud.servicebroker.exception.ServiceBrokerException: Error updating application null (Error parsing xs-security.json data: Inconsistent xs-security.json: grant-as-authority-to-apps or granted-apps not supported on null)
      the xs-security.json is exactly like what you have mentioned.

       

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

      Hi Thanu Kumaraswamy ,
      thanks a lot for your feedback!

      With respect to the error you've received: this appears to be a temporary problem on xsuaa, which seems to be already solved.
      Can you just try it again?
      Kind Regards,
      Carlos

      Author's profile photo Thanu Kumaraswamy
      Thanu Kumaraswamy

      Hi Carlos Roggan

      The issue seems to be still not resolved. It appears both in my trial account and in a GCP(US Central) based account.

      Regards,

      Thanu

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

      Hi Thanu Kumaraswamy ,
      I've just tried it and it works fine on my account.
      I've created an instance of jobscheduler.
      Then an instance of xsuaa, which refers to the already created jobschedulerinstance

      In your error message, it seems that the creation of xsuaa is failing?
      It seems that xsuaa is looking for the jobscheduler instance?
      Or am I wrong?
      You can contact me via personal message to have more troubleshooting
      Cheers,
      Carlos

      Author's profile photo Sachin Baral Ramesh
      Sachin Baral Ramesh

      Hi Carlos Roggan ,

      I am facing the same issue while creating xsuaa broker service, Could you please help me here?

      Regards,
      Sachin

      Author's profile photo Tomer Berman
      Tomer Berman

      Hi Carlos Roggan,

      I am facing the same issue while creating\updating xsuaa broker service

      cf update-service xsuaawithscopeandgrant -c ./xs-security.json

      Please help me here?

       

      Best regards,
      Tomer

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

      Hello,
      I've run again through the sample app and for me it works.
      I've copy&pasted everything from the blog post.
      I'm working on windows.
      I', so sorry, but I have no clue what could be the issue with your xsuaa....

      Author's profile photo Tomer Berman
      Tomer Berman

      Thank you Carlos for your quick respond

      If the CLI command for updating xsuaa service doesn't work how can give permission to the users to run job scheduler through REST API? can I update it in a different way?

       

      Author's profile photo Tomer Berman
      Tomer Berman

      Thank Carlos for your quick response

      If the CLI command for updating xsuaa service doesn't work on the cloud how can give permission to the users to run Job scheduler through REST API? can I update it in a different way?

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

      Tomer, I cannot give prof advice here, but I guess the workaround would be to recreate the service instance. Means to unbind, delete, create, bind manually:
      Example for bind command:

      cf bs myApp myXsuaa

       

      Author's profile photo sajjad afridi
      sajjad afridi

      I have the same issue when running a job from the scheduler I got an unauthorized 401 error, but when hitting from the POSTMAN it working normally. I don't know what is the problem. I tested with all scenarios but with no luck.

       

      Any help or clues will be appreciated.

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

      Hi sajjad afridi ,
      In case you're using MTA, pls find a snippet here:
      Using Job Scheduler in SAP BTP [14]: FAQs | SAP Blogs

      Otherwise the only steps I know are in troubleshooting post.
      Hope that could give you some hints.
      Cheers,
      Carlos

      Author's profile photo sajjad afridi
      sajjad afridi

      Thanks, Carlos Roggan for your quick response I will inform you shortly if it works.

      Author's profile photo sajjad afridi
      sajjad afridi

      ok I got the issues hereafter adding debug variable

      When hitting from POSTMAN with auth 2.0 token i got

       2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] ERR 2021-12-12T19:08:03.829Z xssec:validators configured JwtAudienceValidator with clientId sb-dev-multitenantbasic!t51755
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] ERR 2021-12-12T19:08:03.830Z xssec:validators configured JwtAudienceValidator with clientId dev-multitenantbasic!t51755
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT 2021-12-12T19:08:03.830Z xssec:securitycontext 
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT Application received a token of grant type "password".
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT 2021-12-12T19:08:03.830Z xssec:securitycontext 
      
      
      
       2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT 2021-12-12T19:08:03.830Z xssec:securitycontext Obtained scopes: [
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT     "openid",
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT     "uaa.user"
         2021-12-12T19:08:03.83+0000 [APP/PROC/WEB/0] OUT ]

      But when hitting the same endpoint from job scheduler:

      2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR 2021-12-12T19:10:46.032Z xssec:validators configured JwtAudienceValidator with clientId sb-dev-multitenantbasic!t51755
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR 2021-12-12T19:10:46.032Z xssec:validators configured JwtAudienceValidator with clientId dev-multitenantbasic!t51755
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR 2021-12-12T19:10:46.032Z xssec:validators 
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR Jwt token with audience: [ 'uaa', 'sb-jobscheduler' ] is not issued for these clientIds: [ 'sb-dev-multitenantbasic!t51755', 'dev-multitenantbasic!t51755' ].
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR [cds] - {
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   statusCode: 401,
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   code: '401',
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   message: 'Unauthorized',
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   id: '1501893',
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   level: 'WARNING',
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR   timestamp: 1639335136003
         2021-12-12T19:10:46.03+0000 [APP/PROC/WEB/0] ERR }
      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi sajjad afridi ,
      from your snipppets we can see:
      In case of postman you're using "passwort owner credential", so the result is not comparable.
      You must be adding your user password, right? And your user has the required roles assigned, right?
      In case of jobscheduler, the OAuth mechanism is the "client credentials" Oauth flow.
      It requires that scopes (roles) are assigned via the GRANT statement.
      However, from the snippet it seems you're using CAP?
      Here the statements are written into the CDS, I'm not familiar here.

      The error in the snippet shows the problem: your clientid is not written into the "aud" claim.
      The aud claim is a property in the JWT token.
      And the reason: your clientid is only written into the aud, if you define a scope and if the scope is GRANTED.
      I explained it in detail here: https://blogs.sap.com/2020/09/03/outdated-sap_jwt_trust_acl/

      Kind Regards,
      Carlos

      Author's profile photo max jessi
      max jessi

      Hi Carlos,

      I have same issue, and I believe that the root cause is that in your commands you are using the plan "standard" for the jobscheduler service, but in trial accounts is available the plan "lite", could you confirm that the xsuaa update command/service instance creation works for the plan lite of jobscheduler service also?.

       

      Kind Regards.

      Max.

      Author's profile photo Frank Meertens
      Frank Meertens

      Hi Carlos,

       

      Very nice and insightful blog. I have incorporated this setup into a project and works well.

      Now, I would like to add asynchronous mode as describe in the following help pages: https://help.sap.com/viewer/07b57c2f4b944bcd8470d024723a1631/LATEST/en-US/d9fd81ccabe2410f8b2434311d43c733.html

      1. Adding status 202 to the response provides the asynchronous acknowledgement.  Works fine.
      2. Once the job is completed, I am using the following code to call the REST API for the job scheduler.
      		const jobService = await cds.connect.to("job-scheduler");
      		const tx = jobService.tx(req);
      		const data = {
      			success: true,
      			message: "Long running update for sync runs completed.",
      		};
      		const headers = {
      			"Content-Type": "application/json",
      		};
      		await tx.send({
      			method: "PUT",
      			path: `/scheduler/jobs/${jobId}/schedules/${scheduleId}/runs/${runId}`,
      			data,
      			headers,
      		});

      The job-scheduler service has been declared in the package.json and uses the local binding (default-env.json) that contains the binding of the application with the job scheduling service.

      The call returns "Error during request to remote service: Request failed with status code 403".

      I have tested with Postman and noticed that the UAA token request works when using "grant_type=client_credentials" and not when using password credentials. Is there a way to configure cds.connect to do this? Or should I do this "manually" with a token and job scheduler API request?

       

      Thanks,

       

      Frank.

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

      Hello Frank Meertens ,

      thanks for the feedback, very nice to hear that it works fine for you.
      WRT async job, I think you might find missing pieces of information here:
      https://blogs.sap.com/2020/04/23/using-job-scheduler-in-sap-cloud-platform-5-long-running-async-jobs/

      In case there are doubts wrt CAP programming model itself, please use the respective CAP channel for questions or issues, as I'm not familiar with it.

      Kind Regards,
      Carlos

       

      Author's profile photo Frank Meertens
      Frank Meertens

      Thanks for the additional link, Carlos.  For some reason I had missed that one.  I have this implemented at the moment and will follow-up on the CAP channel to see if there is a shortcut to leverage the job scheduler service as a remote service.

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

      OK, thanks Frank Meertens

      Author's profile photo Jason Muzzy
      Jason Muzzy

      Hi Carlos Roggan!  Thank you for such a detailed blog series!  I have my first job up and running for one app, and now I am working on another.  This 2nd app has its own UAA instance, but I'm using the same job-scheduler instance.  When I run a job against the 2nd app it fails with a 401 unauthorized.  I implemented your app "troublemaker" to inspect the contents of the token, and see that it only contains the scopes from the first app, even after running your "laundry" program.  If I create a new instance of the job scheduler and bind that to my 2nd app then it works fine.  So it makes me think that job-scheduler is ignoring the grant-as-authority-to-apps statement in my xs-security.json file for the 2nd app only for some reason.  Do you have any suggestions on how to get 2 scopes from 2 different UAAs added to the same job-scheduler instance?

      Thank you for your help!!

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

      Hi Jason Muzzy ,
      Thank you for the feedback !
      First of all, yes, it should work and you’ve done correctly.
      However, due to token caching feature of Jobscheduler, it may take a while until changes take effect.
      See documentation: https://help.sap.com/viewer/07b57c2f4b944bcd8470d024723a1631/Cloud/en-US/745ca502face47af9adb546a916ce1e8.html
      To troubleshoot this, you can manually fetch a token from the Jobscheduler binding and check if the token is available there.
      Check out the guided answers here: https://ga.support.sap.com/dtp/viewer/#/tree/2797/actions/40871:42199:42200:41483

      Kind Regards,
      Carlos

      Author's profile photo Jason Muzzy
      Jason Muzzy

      Thank you Carlos Roggan!  I waited 12+ hours and it started working as expected!

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

      Thank you, Jason Muzzy, for the feedback, good to know that it worked and also good hint for other readers as well
      Cheers,
      Carlos