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

403 – Forbidden … scope missing in JWT … aaargh!

Scenario: we’re calling an endpoint which is protected with OAuth 2 and scope.
Problem: although fetching a valid JWT token, were getting 403 error, because of missing scope.
Solution: this blog post 👍
Environment: SAP BTP. Cloud Foundry environment, XSUAA

Quicklinks:
Takeaway
Sample Code

Content

0. Introduction
1. Create Backend Application
2. Create Frontend Application
3. Run the Scenario
4. Optional: Manual Request with REST Client
Appendix: Sample Project Files

Introduction

Our scenario is as follows:

We have an application running in the cloud.
I like to call it Backend Application, to make clear that it is consumed by others.
It is protected with OAuth 2.0 and in addition it requires a certain scope.
We know about scopes:
They are defined by the application and they are assigned as “roles” to a user.
Later, when the user does login, he receives a JWT token which contains these scopes.
Fine.

While this sounds like a Happy End, the story has not finished.
We want to call that backend app from a different application.
I like to name it Frontend Application to make clear that it calls the backend app.
When our frontend app calls the backend app, the request fails with
HTTP Status 403 – Forbidden.
Background:
Status 403 means that the request is somewhat correct, in terms of URL and authentication, but the app rejects it, because authorization is wrong.

We don’t understand why it fails?
If we try calling the backend app with human user, then the request works fine.
The JWT token which is sent after user-login contains the required scope.
But when we send the request in app-to-app scenario, then the JWT token doesn’t contain the required scope. And the request fails.

How does it come?
With other words:
The OAuth flows password-credentials or Authorization Code are working fine.
But the client-credentials flow doesn’t work.

The problem is:
In user-centric scenario, the scope is assigned to the user when assigning the corresponding role.

The solution is:
In app2app scenario, this assignment has to be done as well.
To do this assignment, we need to declare it in the security-configuration, aka xs-security.json file.
And that is already all we need to do.
Add one statement to xs-security.json, stop reading this blog (give it a like…) and be happy.
Finally, the Happy End.

While the Happy End has been reached…. The story doesn’t end here.
To make things reproducible, as usual, let’s get our hands dirty and go through a hands-on tutorial.

The hands-on scenario:

We create an instance of XSUAA.
It is used to protect the backend app and to generate a token to access the app.
We create the backend app.
We create the frontend app which calls the backend app.

Below diagram shows the security configuration which defines a scope and makes sure that the issued JWT token contains that scope in client-credentials flow.

Note:
In case you’re not interested in frontend app, you may find useful information about manual request with REST client in chapter 4.

Prerequisites

To follow the tutorial, we need:
Access to SAP Business Technology Platform (SAP BTP) Cloud Foundry environment.
Basic knowledge of Node.js
We use the command line client for Cloud Foundry, but the same can be achieved in the cockpit.
Basic knowledge about OAuth 2.0 flows is expected.

Preparation

To give clear guidance, I’m describing my project layout on my Windows machine.
You can of course ignore it or adapt to your needs.

1. Create Project

On filesystem, we create a root project folder C:\scopetest   containing 2 subfolders for the 2 applications.
We create the required files in the folders and copy the content from the Appendix.

C:\scopetest
backend
package.json
server.js
frontend
package.json
server.js
manifest.yml
xs-security.json

Or see this screenshot:

2. Create Instance of XSUAA Service

In our scenario, we have just one instance of XSUAA service.
It is bound to the backend application and used to protect it.
The consumer application (Frontend), is bound as well to the same instance.
Since both apps are bound to the same instance. we don’t need to “grant” any scope (see this blog post for detailed explanation about “grant”).

Our scenario doesn’t contain any human user – to make the scenario small and clear and focus on the relevant topic: “How to get the scope into the JWT token in case of client-credentials”.
As such, we don’t need any “role-template”.

So in our security descriptor, we call it xs-security.json (the usual name, but not mandatory name) we define a scope with name “myscope”.

"scopes": [{
    "name": "$XSAPPNAME.myscope"

Defining a scope doesn’t mean that it is also assigned.
In case of human user, the scope is assigned by cloud admin via role and role collection.
In case of client-credentials, the scope is assigned by accepting it.
Since we have only one XSUAA instance, we don’t need to grant it.
But still we need to accept it.
This is done with the authorities statement:

"authorities":["$XSAPPNAME.myscope"]

We use this statement to declare that our OAuth client (with name “myxsapp”) accepts the assignment of scope $XSAPPNAME.myscope

That’s already all the learning of today’s tutorial.
Nevertheless, the brave among us, continue with the hands-on session to get the full understanding and happy end experience.
The full file content can be found in the Appendix.

To create the service instance, we jump into directory C:\scopetest and execute the following command:
cf cs xsuaa application myXsuaa -c xs-security.json

1. Create Backend Application

After creating the service instance, we can go ahead and create our backend application.
The app is bound to the instance of XSUAA service, which is used to protect and endpoint of the app.
The app does really nothing but exposing one endpoint, which is protected with OAuth and which requires the scope which we defined in the xs-security.json file.
We check the scope manually in the code and we return a status code 403 if we don’t find it in the JWT token.
For protection and scope check, we use the library @sap/xssec

app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
   . . .
   const auth = req.authInfo  
   if (! auth.checkScope(UAA_CREDENTIALS.xsappname + '.myscope')) {
     res.status(403).end(`Forbidden. Missing authorization...`)

In addition, we read the “scope” claim from the JWT token, we print it to the console and we also return it in the response of our endpoint.
This is just for fun and for celebrating the happy end:

app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
    const token = req.authInfo.getAppToken()
    const tokenInfo = new xssec.TokenInfo(token)
    const scopes = JSON.stringify(tokenInfo.getPayload().scope)
   . . .
   res.send(`Backend was called successfully. Received JWT token with scopes: ${scopes}`)

The full application code can be found in the appendix.

We don’t deploy the backend app yet because we have just one single manifest file for both our apps. We deploy both apps together in chapter 2.

2. Create Frontend Application

The purpose of the present blog post is to solve the problem of missing scope.
This has been already solved by creating the XSUAA service instance with proper configuration.
To test the solution, we can either call our backend app manually with a REST client like postman (See chapter 4).
Or we can create a frontend app which consumes the backend app.
I think this second approach is easier and comes closer to reality.

The frontend application is not protected and doesn’t facilitate user-login.
It is not necessary, because the app is just used to call the protected backend app.
To fetch a JWT token, the app is bound to the same XSUAA instance like the backend app.
We use a helper function of the library @sap/xssec to fetch a token via client-credentials flow

xssec.requests.requestClientCredentialsToken(null, UAA_CREDENTIALS, null, null, (error, token)=>{
   resolve(token)

Once we have the token, we use it to call the endpoint of backend app.
We use the native https module for the GET request:

host: 'mybackend.cfapps.sap.hana.ondemand.com', 
path: '/endpoint',
headers: {
   Authorization: "Bearer " + jwtToken
}

https.get(options, res => {
   let response = ''
   res.on('data', chunk => {
      response += chunk
   })
   res.on('end', () => {
      resolve(response)

Finally, our simple frontend app prints the response to the browser screen.
As mentioned above, the response just prints the scope name which were found in the JWT token.
The full application code can be found in the appendix.

3. Run the Scenario

We can go ahead and jump into folder C:\scopetest and deploy our 2 app modules to Cloud Foundry.
Now we can open our frontend application.
In my example:
https://myfrontend.cfapps.sap.hana.ondemand.com/app

And the result, :

The response contains the scopes claim of the JWT token which we’ve sent to the backend app.
We can see that the request has been successful and that the required scope has been added to the JWT token..
Like that, we can be convinced that the learning of this tutorial really helped to get the scope into the JWT token.

Happy End…

🥳

4. Optional: Manual Request with REST Client

In case you need to call the backend app with REST client from local laptop, let’s quickly go through this scenario as well.
We use a REST client to execute the same 2 requests that we did in our frontend app:

1. fetch JWT token from XSUAA authorization server
2. use the token to call the endpoint of our backend application.

4.0. Preparation: Credentials

In order to fetch a JWT token from the XSUAA instance, we need credentials, used to authenticate against the XSUAA authorization server.
In case of bound application, we get the credentials in the environment variables of our application (after binding and deployment).
In case of remote app (like a REST client) we can create a service key and view the credentials there.

To view the credentials in the application environment:
cf env mybackend

To create a Service Key:
cf csk myXsuaa sk

To view the content of the Service Key:
cf service-key myXsuaa sk

In both cases, we can see the properties which we need, as follows:

We take a note of these 3 relevant properties and values:

“clientid”: “sb-myxsapp!t14860”
“clientsecret”: “EP/xZARlzygmKlJ92Uu5sE9x/Go=”
“url”: “https://test.authentication.sap.hana.ondemand.com”

4.1. Fetch JWT Token with REST Client

Compose a new request in postman with the following settings:

HTTP Verb:
POST

URL:
We need to append the segments
/oauth(/token
to the “url” property.
In my example:
https://test.authentication.sap.hana.ondemand.com/oauth/token

Headers:
Name: Content-Type
Value: application/x-www-form-urlencoded

Request Body:
client_id:sb-myxsapp!t14860
grant_type:client_credentials
response_type:token

Authorization:
Type: Basic
User: value_of_clientid
Password: value_of_clientsecret

Below screenshot tries to show all required settings:

After successful request, we see a response body with several properties.
We copy the value of the access_token property into our clipboard.

Optional: Introspect the JWT Token

The JWT token is a decoded string.-..to keep it secret……. and like all children, we’d like to know what is it, this secret string…..🙊
As such, we use a tool to decode the mystic token, with that strange name, and view the secret content….

And example for such a tool: jwt.io -> debugger
It shows that the desired scope is present:

4.2. Call Service with REST Client

We still have the JWT token in our clipboard, so we can go ahead and compose the second request, which is the call to our backend app endpoint.

HTTP Verb:
GET

URL:
https://mybackend.cfapps.sap.hana.ondemand.com/endpoint

Authorization
Authorization Type: Bearer Token
Authorization Value: just the JWT token in clipboard.
No need to enter the literal “Bearer ”, as it is generated.

In my example:

4.3. Optional: negative test

For reasons of curiosity:
We can remove the “authorities” statement from the xs-security.json file.
Then update the XSUAA service instance and run the scenario:
It should fail with our “Forbidden” error.
The update command:
cf update-service myXsuaa -c xs-security.json

5. Optional:  cleanup

For your convenience, find below the commands to delete all artifacts created during this tutorial:

cf d -r -f myfrontend
cf d -r -f mybackend
cf dsk -f myXsuaa sk
cf ds -f myXsuaa

Summary

We have a scenario where a protected app is bound to one XSUAA.
We want to call that app, so we fetch a JWT token from this XSUAA.
We want that this JWT token contains the required scope.
To achieve this, we need to add the “authorities” statement to the xs-security.json file.

Takeaway

Define scope as usual:

"scopes": [{
    "name": "$XSAPPNAME.myscope"

Accept scope (root level property)

"authorities":["$XSAPPNAME.myscope"]

Links

Tutorial for granting scopes.
OAuth for dummies, explained by Dummy.
Info about the content of JWT tokens, explained in my dummy way.
npm site for xssec library.

Reference for xs-security.json file in the SAP Help portal.
Understanding Token Exchange


Appendix: Sample Project Files

xs-security.json

{
    "xsappname": "myxsapp",
    "scopes": [
        {
            "name": "$XSAPPNAME.myscope"
        }
    ],
    "authorities":["$XSAPPNAME.myscope"]
}

manifest.yml

---
applications:
  - name: myfrontend
    path: frontend
    memory: 64M
    routes:
    - route: myfrontend.cfapps.sap.hana.ondemand.com
    services:
      - myXsuaa
  - name: mybackend
    path: backend
    routes:
    - route: mybackend.cfapps.sap.hana.ondemand.com
    memory: 64M
    services:
      - myXsuaa

Backend Application

package.json

{
  "dependencies": {
    "@sap/xsenv": "latest",
    "@sap/xssec": "latest",
    "express": "^4.17.1",
    "passport": "^0.4.0"  
  }
}

server.js

const xsenv = require('@sap/xsenv')
const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa

const express = require('express')
const app = express();
const xssec = require('@sap/xssec')
const passport = require('passport')
const JWTStrategy = xssec.JWTStrategy
passport.use('JWT', new JWTStrategy(UAA_CREDENTIALS))
app.use(passport.initialize())
app.use(express.json())


// start server
app.listen(process.env.PORT)

// Endpoint to be called by frontend app
app.get('/endpoint', passport.authenticate('JWT', {session: false}), (req, res) => {
    const token = req.authInfo.getAppToken()
    const tokenInfo = new xssec.TokenInfo(token)
    const scopes = JSON.stringify(tokenInfo.getPayload().scope)

    const auth = req.authInfo  
    if (! auth.checkScope(UAA_CREDENTIALS.xsappname + '.myscope')) {
        res.status(403).end(`Forbidden. Missing authorization. Received JWT token with scopes: ${scopes}`)
    }      

    res.send(`Backend was called successfully. Received JWT token with scopes: ${scopes}`)
})





Frontend Application

package.json

{
  "dependencies": { 
    "@sap/xsenv": "latest",
    "@sap/xssec": "^3.2.12",
    "express": "^4.17.1"
  }
}

server.js

const https= require('https')
const xssec = require('@sap/xssec')
const express = require('express')
const app = express();
const xsenv = require('@sap/xsenv')
const UAA_CREDENTIALS = xsenv.getServices({myXsuaa: {tag: 'xsuaa'}}).myXsuaa

// start server
app.listen(process.env.PORT)

// app endpoint
app.get('/app', async (req, res) => {
   const jwtToken = await _fetchToken()      
   const response = await _callBackend(jwtToken)   

   res.send(`Response from Backend Application:<br>${response}</br>`)
})


/* HELPER */

async function _fetchToken (){
   return new Promise ((resolve, reject) => {
      xssec.requests.requestClientCredentialsToken(null, UAA_CREDENTIALS, null, null, (error, token)=>{
         resolve(token)
      })  
   })  
}

async function  _callBackend(jwtToken) {
   return new Promise ((resolve, reject) => {
      const options = {
         host: 'mybackend.cfapps.sap.hana.ondemand.com', 
         path: '/endpoint',
         headers: {
            Authorization: "Bearer " + jwtToken
         }
      }

      https.get(options, res => {
         let response = ''
         res.on('data', chunk => {
            response += chunk
         })
         res.on('end', () => {
            resolve(response)
         })
      })
   })   
}

Assigned Tags

      5 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Mustafa Bensan
      Mustafa Bensan

      Hi Carlos,

      Thanks for the thorough explanation and examples.  I was wondering how the solution would need to be adapted if the Backend Application was a multi-tenant CAP service?

      Regards,

      Mustafa.

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

      Hi Mustafa Bensan ,
      thank you for the question, this is something I probably should put on my TODO list 😉
      I'm not familiar wit CAP, so cannot give appropriate advice.
      As far as I know, CAP generates the security descriptor file (xs-security.json) So you need to check out the CAP documentation to find which annotation is responsible to generate the "authorities" statement.
      On the other hand, isn't is possible to modify the xs-security.json file after generation? So manually add that statement?
      At the end of the day, this is a mechanism on XSUAA level, it doesn't depend on the language or programming model of the app.

      About multitenancy:
      As usual, here things get complicated.
      The XSUAA instance will be "shared", such that the tenant gets a kind of "copy" of the provider XSUAA-instance. How is your setup? The client which calls your endpoint, is it created in the subscriber account? If yes, it cannot be bound to the same instance, as described in this blog post.
      Is your client a local REST client? If yes, you can create a service-key for the XSUAA instance and use the credentials just like in single-tenant scenario.
      Hope this has helped at least a little bit.
      Kind Regards,
      Carlos

      Author's profile photo Mustafa Bensan
      Mustafa Bensan

      Thanks Carlos Roggan!  You have certainly given me some options to think about.

      Regards,

      Mustafa.

      Author's profile photo Srinivas Rao
      Srinivas Rao

      Hi Carlos Roggan  - As always, you cut a big elephant into right sized pieces, so that it can be digested well. Thanks for your blogpost. I am learning to develop cloud native solutions and hence I have following questions: -

      1. What is passport for?
      2. How does the "req" object fills up for the endpoint ? In our other use case that we are building, the req is always undefined. Is it to do with the XSUAA instance binding with the application?

       

      Thanks & Regards

      Srinivas Rao.

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

      Hello Srinivas Rao ,
      thanks for the feedback, this is useful, like all your feedback.
      In some other blog post I gave little bit more background.
      The server app is based on "express" a very common FWK in the node world.
      It allows also to be extended with so-called "middleware", and "passport" is a heavily used middleware that takes care of authentication. The middleware is invoked by express, before the request is passed to us (endpoint implementation).
      As such, if our endpoint is e.g. invoked without JWT token, then passport will reject it and our implementation is not even reached.

      In addition, passport itself is extendable. In above example, it is configured with a "JWTStrategie" which is provided by xssec. This is a node library (module) which is built by SAP for XSUAA.
      This lib knows how to validate JWT tokens which were issued by XSUAA.

      One thing which is done by xssec, for convenience:
      If the validation is successful, it creates a convenience object, the authInfo, and it fills it with information which is extracted from the JWT token.
      This authInfo object is then attached to the req object.
      This is just convenience, we could as well manually decode the JWT token and manually do the scope-check.

      The req object itself should always be filled, as this is typical REST. Some client calls our endpoint and we need to be provided with some context information about the request itself (e.g. headers, request body). This is typically passed to us in the method signature. Similarly is it done in java FWK.
      In your case, I don't know, you need to check out your environment.

      Kind Regards,
      Carlos