Technical Articles
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
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
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(){})
Hi Carlos, this is very helpful information!
Hey Jens, thanks very much!
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...
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
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?
Hi Atul Shrivastava, hum, that's interesting, let's continue troubleshooting via personal messaging
Sure, pinged you on personal window.
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 updating xsuaa service using xs-security.json
But when I remove $XSSERVICENAME from xs-security.json as shown below –
Hi @Shreyasi Chakraborty
did you manage to solve the issue? if so can you please share how?
Regards,
Thanu
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.
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
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
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
Hi Carlos Roggan ,
I am facing the same issue while creating xsuaa broker service, Could you please help me here?
Regards,
Sachin
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
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....
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?
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?
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:
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.
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
Thanks, Carlos Roggan for your quick response I will inform you shortly if it works.
ok I got the issues hereafter adding debug variable
When hitting from POSTMAN with auth 2.0 token i got
But when hitting the same endpoint from job scheduler:
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
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.
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
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.
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
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.
OK, thanks Frank Meertens
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!!
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
Thank you Carlos Roggan! I waited 12+ hours and it started working as expected!
Thank you, Jason Muzzy, for the feedback, good to know that it worked and also good hint for other readers as well
Cheers,
Carlos