Skip to Content
Technical Articles
Author's profile photo Alessandro Biagi

Build a Multi-Tenant User Management Microservice in BTP with CAP

NOTE: this blog post is intended for developers who have previous experience in developing multi-tenant CAP applications using SAP Business Application Studio, SAP BTP destinations, and the destination and XSUAA services.


After I published this blog post, many developers reached out to me with the classical question: “does this microservice work in a multi-tenant scenario?

My first reaction was to provide them a quick and straightforward answer: “yes, as long as you properly handle the on-board and off-board processes for the subscription and the access to the appropriate subscriber XSUAA APIs to manage its users“.

Looks quite simple, right? Well, but, of course, the queries kept coming (having the same quick and straightforward answer). Then, I finally made the decision to take on the challenge and develop the multi-tenant version of the microservice to serve as a definitive reference for those who want to embarc on that adventure.

So, before you continue reading this post, I strongly recommend that you read through the first post (as this one is just its spin-off) to fully understand what’s going to be discussed in the following topics. That post explains in detail the single-tenant version of the microservice – this version just contains some specific modifications to make it multi-tenant.

Therefore, without further due, let’s dive into the service details.

Application Architecture

The business problem is exactly the same of the first post. The scenario in this post is intended to deep dive into a fully working multi-tenant version of the CAP microservice leveraging an SAP Fiori UI for subscribers. The service manages users who are assigned to specific role collections (the ones belonging to the business application that is consuming the service) reading and writing user information from BTP in the subscriber subaccount using its XSUAA service APIs. The microservice is ready to deploy on BTP, Cloud Foundry Runtime.

Here’s a diagram representing the overall architecture of the microservice:

Figure 1 – Application Architecture

You may have noticed that the contents inside the provider subaccount are exactly the same as in the architecture described in the first post.

In the subscribers subaccounts, there’s the microservice subscription and a specific destination pointing to the XSUAA APIs from the subaccount.

XSUAA manages the BTP user store (namely “Shadow Users”). In the subscriber subaccount the CAP microservice subscription is created and  will be responsible for managing the business application’s users in there. Once again, as in the original version, the microservice will “filter” users from BTP based on a set of role collections belonging to the business application.

It interacts with the XSUAA service in the subscriber through a destination to make REST API calls to the services’ user management APIs which are based on the System for Cross-Domain Identity Management (SCIM) standard.

That being said and looking to the architecture diagram, it’s obvious that each subscriber will require its own instance of the XSUAA service with the apiaccess plan (which also requires enabling Cloud Foundry and creating a CF space – no memory quota assignment is needed in this case). This is well detailed in the Git repo branch instructions.

The Fiori Elements HTML5 application will serve as the UI for the microservice’s subscribers.


As in the first post, that application is already built using SAP Cloud Application Programming Model (CAP) and BTP’s XSUAA APIs. The full code can be found in this branch of the GitHub repo from SAP samples.

As previously mentioned, the intent of this blog post is just to walk through and understand the key-points of the development.

Therefore, as first prerequisite to follow-up with this post (besides reading through the first one), you must clone the repo branch strictly following the instructions from its to setup the project locally.

NOTE: do not miss any single step of the instructions, otherwise you won’t be able to run and deploy the application.

Security Descriptor (xs-security.json)

The only difference in the security descriptor from the first post is that “tenant-mode” is shared instead of dedicated:

Figure 2 – Application info

We also changed the “xsappname” in case you decide to deploy the microservice in the same subaccount of the single-tenant version, otherwise it’s absolutely not mandatory.

The role collections for the generic application served by the microservice have been prefixed with GenericMtxApp instead of GenericApp also in case you want to deploy this version in the same subaccount as the single-tenant version, thus avoiding to share the same role collections between both versions. And that’s all!

To learn more about the syntax of the XSUAA security descriptor you can read this official document.

Required Services

Same as in the first post. The only exception is that, upon deployment the MTA will create an additional service to manage the on-board and off-board processes of subscriptions to the microservice: the SaaS Provisioning Service (namely saas-registry). That service is bound to the CAP backend service in the mta.yaml:


Figure 3 – New service binding for the CAP backend service in mta.yaml

And is configured as shown below:


Figure 4 – SaaS Provisioning Service configuration

I strongly recommend you examine the mta.yaml in detail to look for the differences in relation to the single-tenant version from the first post. You’ll notice that the managed approuter has been replaced by a standalone approuter:


Figure 5 – Standalone Approuter in mta.yaml

Thus, all references to the HTML5 repo and runtime services and respective configurations have been completely removed. This also leads to changes in the approuter configuration within the xs-app.json file:


Figure 6 – xs-app.json

Besides the replacement of all routes related to the HTML5 repo and runtime, another route has been added to point to the on-subscription and on-dependencies callbacks required by the SaaS Provisioning Service.

Destination Setup

Same as in the first post.

Package Setup

Few modifications have been made to the projects’ package.json file in relation to the first post.

First, some dependencies have been added to handle the on-board, off-board and get dependencies processes of subscription life-cycle:


Figure 7 – New project dependencies

The @sap/cds-mtx package is also responsible for managing the different schemas (HDI containers in the case of SAP HANA Cloud) where the data model is deployed for each tenant (subscriber) to provide full data isolation.

Second, some changes have been made to the cds.requires section:

1. Indication of a “mock tenant” for each “mock user“;


Figure 8 – Mock tenant for mock user

2. Multitenancy setup:


Figure 9 – Multitenancy setup

3. There’s also an additional API setup which is not part of the original microservice and has been added as a bonus that will be discussed later:


Figure 10 – Additional API reference

Microservice Configuration

The new GenericMtxApp prefix for the role collections has also been adjusted in the corresponding environment variable in the microservice configuration:


Figure 11 – New role collections prefix

Service Definition

In the service definition, a new action has been bound to the User entity. This has been done to execute the bonus snippet, which will be discussed later in this post:


Figure 12 – New action bound to the User entity

Annotations for the UI

Like it has been done with the previously defined action, we also annotate the criticality of the newly added action (which is also non-critical), but with no side effect in this case:


Figure 13 – UI annotations for the new action

Subscription Handling

The subscription handling process is fully implemented in the server.js file into the srv folder. That file has been entirely generated by the SAP HANA Academy CAP SaaS Multi-tenant Application Generator, selecting the options to automatically create routes and return required destination dependency in the respective callbacks, with no further modification (please, refer to the corresponding videos in the playlist from the link to better understand the generator).

Code Analysis

The application code is basically the same with the only exception that, as it’s running in a multi-tenant context, the control flag that was a simple boolean variable in the single-tenant version has been changed to a JSON object like demonstrated in the screenshots below:


Figure 14 – New JSON object control flag


Figure 15 – Control flag handling upon data read


Figure 16 – Control flag reset upon data refresh

Bonus Snippet!

A while ago I also wrote and published a blog posts series focusing on how to get authenticated user information using three different approaches. One of them (which is the most complete) uses the XSUAA API from the application service binding – you can check it here.

And guess what? Same old classical recurring question: “does it work in a multi-tenant context?“. Well, in this case the answer is: “yes, but with a slight modification in the code of the post to consider the authentication made in the subscriber“.

So, once again, I decided to take advantage of the effort to produce the mult-tenant version of this microservice and add the multi-tenant version of the procedure to get authenticated user information.

NOTE: to understand what I’m talking about here, please also read through the blog post, before inspecting the bonus snippet.

OK, so here’s the code executed by the previously mentioned userInfo action:


Figure 17 – userInfo action main code

In the single-tenant version described in the post, the url in the credentials is simply filled with the one gotten from the XSUAA service binding. As this binding is done in the provider subaccount (where the service is actually deployed), when the “/userinfo” endpoint is invoked XSUAA yields error, because the authentication (handled by the approuter) has been done in the subscriber subaccount and not the provider.

Well, the trick here is simple: as subscriptions can be done only in subaccounts located in the same region as the provider we can safely assume the authentication domain is always the same for both provider and subscribers. Therefore, for XSUAA to correctly interpret the authenticated user info, we just need to change the subdomain of the API URL to point to the subscriber subdomain instead of the provider. This is exactly what the highlighted code snippet does.

And this concludes the analysis and explanation of the multi-tenant user management microservice CAP project (with bonus snippet).

Additional Resources

Here’s a list of resources to enhance your learning experience on this topic:


After setting up the project from this git repo branch and going through this blog post content, you’ll have learnt and experimented how to build a multi-tenant CAP microservice which will allow your business application to manage its business users autonomously for each application subscriber. Hope you have enjoyed the journey!

Please, do not hesitate to submit your questions in Q&A in SAP Community:

Assigned Tags

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

      Hi Alessandro Biagi,

      wow, a big shout-out from my side for this awesome blog post  🎉 It was seriously enlightening and very well-written, providing excellent guidance and instructions needed to develop a cool microservice for user-managment in a SaaS context. 👍

      For anyone interested to learn more on this topic in a Cloud Foundry and Kyma context... 😄 My colleague Alper Dedeoglu and I actually published various blog posts and sample applications just recently, offering a similar in-app user management feature! 🙌 Talk about great minds thinking alike, huh? 😄 Thanks for linking us in your blog post Alessandro - just highlighting it once again.

      SAP BTP, Kyma Runtime

      SAP BTP, Cloud Foundry Runtime

      Keep up the fantastic work, Alessandro Biagi! Looking forward to more exciting content from you. 🚀

      Cheers! 🎉

      Author's profile photo Alessandro Biagi
      Alessandro Biagi
      Blog Post Author

      Hi Martin Frick,

      Thank you for the positive feedback!

      Actually, I learned a lot from your blogs and samples, and also expect you keep up the awesome work for developers to keep learning more and more.

      Long live CAP and Multitenancy!


      Author's profile photo Nicolas Lau
      Nicolas Lau

      Hi Alessandro Biagi,

      regarding the "Bonus Snippet!" related to your previous blog.

      I also thought about this and came the following solution. Not sure if this is a good approach, but it works fine for me.

          this.on('userInfoUAA', async req => {
              // Get the XSUAA host URL from service binding
              const xsuaa_bind = JSON.parse(process.env.VCAP_SERVICES).xsuaa[0]
              const jwt = retrieveJwt(req)
              const xssuaDomain = xsuaa_bind.credentials.uaadomain
              const consumerSubdomain = decodeJwt(jwt).ext_attr.zdn
              const api_def = cds.env.requires['xsuaa_api']
              api_def.credentials.url = 'https://' + consumerSubdomain + '.' + xssuaDomain
              // connect to the XSUAA host
              const xsuaa = await
              return await xsuaa.get("/userinfo")


      I now wanted to test your approach, but in my case req.headers.referer is always undefined. And where do you set process.env.APP_URI? Could you show the complete service handler of your solution? Thanks!

      best regards



      Author's profile photo Alessandro Biagi
      Alessandro Biagi
      Blog Post Author

      Hi Nicolas Runge,

      Your approach is also correct (actually there's always more than one way to achieve the same result).

      The referer request header might not be sent by Safari (desktop or mobile) and some other mobile browsers (like Opera mini). It works pretty fine with Chrome, FireFox and MS Edge. However, you may try to fetch the origin header which seems to be always sent by all browsers.

      Referer contains the full requested url, whereas origin contains only the requested domain with no further paths and/or resources, so it may also serve the application's purpose.

      process.env.API_URI is set upon app deployment as defined in the project's mta.yaml file. I guess you haven't read through the entire blog post. The prerequisites topic states that: "The full code can be found in this branch of the GitHub repo from SAP samples.". In the branch's  you'll find detailed instructions on how to locally setup the project and deploy/test the application.

      Best Regards,


      Author's profile photo Nicolas Lau
      Nicolas Lau

      Hi Alessandro,

      thanks for the explanation. Now I realized my mistake. I tested the endpoint not via Browser, instead I used the VSC Rest Client to call the deployed endpoint (of course using a valid token I got in a previous request in combination with grant_type password).

      "I guess you haven't read through the entire blog post." I must admit that is true, therefore sorry for the unnecessary question!

      best regards,