Skip to Content
Technical Articles

Application to multiple XSUAA Services – Replace whitelisting of SAP_JWT_TRUST_ACL

You heard something about whitelisting and something about this crazy “SAP_JWT_TRUST_ACL”? Then you read blog posts, and something about that this variable is deprecated and now you’re confused? I had the same feeling and wanted to understand how this XSUAA json web token things is actualy working – without the SAP approuter. You do wanna understand it either? Go on reading!

 

Intro

As the “SAP_JWT_TRUST_ACL outdated” a few months ago (have a look at Carlos Roggans wonderful post) there is a new way to proceed with “whitelisting” other XSUAA instances. As this is easy doing it with the sap approuter and the xs-security.json files, there are some things to understand doing it without these files.

The target for me was enabling a node.js application, who is going to provide an API , to be protected by some kind of xsuaa.

And be able to get called from the Job-Scheduler Service AND a normal ‘user’ with  oAuth clientID and clientSecret.

 

Note: Please read it with a grain of salt as I’m not the senior expert in XSUAA area 😉

If you absolutley do not know anything about XSUAA, stop reading and have a look at this great post here!

 

What’s in for me?

As you already found in Carlos post things about app2app with 2 instances of XSUAA, you will get  in touch with single-app with 2 instances of XSUAA and without xs-security.json.

This technical blog post will provide you some details about XSUAA, JWT and oAuth tokens, together with a short node.js way to work with.

 

Overview

On a SAP Cloud Platform Cloud Foundry space we’ve a job scheduler service (with xsuaa service integrated) and an additional xsuaa service running. Both are bound to a node.js application called “JOB001”:

 

What do we want to achieve?

As the Job Scheduler service is providing an own xsuaa service, we do have 2 different xsuaa services in place. We learned in Carlos Roggans blog post something about the audience in a JWT and that we’re required to add / share this audience.

Now we can’t share the audiences as we do not have two applications in place like in the referenced post. We do have one application (JOB001) and we have 2 XSUAA instances.

One is a “normal” xsuaa, the other one is the Job Scheduling service. Both want to access the JOB001 application and the api provided inside.

 

Let’s start simple

We’re setting up an easy node.js app with an ‘express’ module. We’re also using the ‘@sap/xsenv’ module to fetch the environment variables (so called ‘VCAP_SERVICES’ – details here).

Then we’re going to read the ‘VCAP_SERVICES’ and get out the xsuaa service credentials. With this, we’re able to create a new JWTStrategy. This json web token strategy is used with module ‘passport’, and gets involved with an middleware ‘use’ of our ‘app’.

 

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

//configure passport - get first audience for xsuaa
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();
app.use(passport.initialize());
app.use(passport.authenticate('JWT', { session: false }));    


app.get('/doSomething', function(req, res){
    res.send('Alles wird gut!');
});

app.get('/jwtdecode', function(req, res){
	if (!req.headers.authorization) {
        res.statusCode = 403;
        res.end(`Missing JWT Token`);
      } else {
        res.statusCode = 200;
        var dec = jwtDecode(req.headers.authorization);
        dec.jti = "censored";	  
      
        res.setHeader('Content-Type', 'application/json');
        res.end(`${JSON.stringify(dec)}`);
      }
});

const port = process.env.PORT || 3000;
app.listen(port, function(){
    console.log('Listening on port ' + port);
});

Now we’re ready! Deploy to your Cloud Foundry environment and call https://your-url.ondemand.com/jwtdecode in postman.

Status: 401 – Unauthorized.

Fine, your endpoint is protected. Good thing.

Now we’re going to use the credentials to request an oAuth V2 token before, and sending this token to the application, we did deploy before.

We’re opening the credentials via Cockpit of our XSUAA instance ‘xsuaa_jobs’:

And going to request the oAuth token at endpoint provided at ‘url’ with ‘clientid’ and ‘clientsecret’:

https://de.authentication.eu10.hana.ondemand.com/oauth/token?grant_type=client_credentials&response_type=token

Then we’re going to add the oAuth token (bearer) to the header as ‘Authorization’ like we know and we receive something from our ‘/jwtdecode’ token like this:

So we do have a now protected API and we’re seeing our json web token decoded. And what’s appearing in the audience? The clientID of ‘xsuaa_jobs’. Nice!

And what’s still not working? The token from the ‘jobscheduler’ service!

Status: 401 – Unauthorized.

Why? The audience from the jobscheduler-service is not existing for JOB001.

 

Adding second xsuaa service

So as we already have bound both service-instances to our JOB001 application, we’re now going to involve both jwt-strategies in our app:

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

/**
 * As we have multiple XSUAAs in place, we need to generate JWTs for everyone.
 * With this tokens we can get the audiences of them and use them for
 * authentification purposes on the express module.
 */
//configure passport - get first audience for xsuaa
const xsuaaService = xsenv.getServices({ myXsuaa: { tag: 'xsuaa' }});
const xsuaaCredentials = xsuaaService.myXsuaa; 
const jwtStrategy = new JWTStrategy(xsuaaCredentials)
passport.use('jwt1', jwtStrategy);

// configure passport - get second audience for job-scheduler
const jobSchedulerService = xsenv.getServices({ jobScheduler: { tag: 'jobscheduler' }});
const jobSchedulerCredentials = jobSchedulerService.jobScheduler.uaa; 
const jwtStrategy2 = new JWTStrategy(jobSchedulerCredentials)
passport.use('jwt2', jwtStrategy2);

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

// if we're running on Cloud foundry, port is awailable and we will need the 2 jwt methods to authenticate!
app.use(passport.initialize());
app.use(passport.authenticate(['jwt1', 'jwt2'], { session: false }));    

app.get('/doSomething', function(req, res){
    res.send('Alles wird gut!');
});

app.get('/jwtdecode', function(req, res){
	if (!req.headers.authorization) {
        res.statusCode = 403;
        res.end(`Missing JWT Token`);
      } else {
        res.statusCode = 200;
        var dec = JWTDecode(req.headers.authorization);
        dec.jti = "censored";	  
      
        res.setHeader('Content-Type', 'application/json');
        res.end(`${JSON.stringify(dec)}`);
      }
});

const port = process.env.PORT || 3000;
app.listen(port, function(){
    console.log('Listening on port ' + port);
});

So what’s that about?

passport.use('jwt1', jwtStrategy);

You can provide another default name of the strategy. If you do not provide this, it’s ‘JWT’ by default.

It’s important to have different names for each strategy:

passport.use('jwt2', jwtStrategy2);

‘jwt2’ contains the uaa credentials of the jobscheduler service, defined as a JWTStrategy (jwtStrategy2).

And the important part: Add both JWTStrategies to the middleware with passport:

app.use(passport.authenticate(['jwt1', 'jwt2'], { session: false }));    

Within this middleware step, we did provide both JWT strategies and therefore both audiences to the JOB001 application.

Final countdown ….

Now it’s time to check, if the other xsuaa service (uaa service from jobscheduler-service, don’t ask why this is only called ‘uaa’) is now working like expected.

So we’re getting the ‘clientid’ and ‘clientsecret’ …

… requesting our oAuth v2 token (bearer), adding that one to the ‘Authorization’ header field and hitting ‘send’ to the endpoint with ‘/jwtdecode’:

It’s working! Don’t trust yourself – do the same thing again with the ‘xusaa_jobs’ service-instance credentials… if this is still working either, we did a good job 🙂

 

Conclusion

Coming back to the blog post from Carlos Roggans. He explained how to add audiences to each application via the xs-security.json. This is the most common approach, but now we’re also able to do this without, by requiring the app to fetch 2 json web token strategies, from 2 xsuaa services.

With that, the job-scheduler service is able to call the endpoint as well as we’re able to do this with the xsuaa credentials.

Be the first to leave a comment
You must be Logged on to comment or reply to a post.