Technical Articles
Multitenancy – Develop and Register Multitenant Application to the SAP SaaS Provisioning Service on the SAP BTP: Hands-on Tutorial on Cloud Foundry
For more general descriptions on how many steps it takes to do from a normal application to a multitenant application, you can read this .
More information on this Series – Multitenancy:
Prerequisites
Business applications
-
If you come with prepared applications project, please skip to the next item.
-
If you come with no project, you can use the application generator tool,
express-generator
, to quickly create an application skeleton by following this tutorial: . Or, you can git clone the skeleton project directly from: .npm install express --save npm install -g express-generator
For more information on how to develop and run business applications on SAP Business Technology Platform (SAP BTP) using our cloud application programming model, APIs, services, tools, and capabilities, see .
BTP account
Here are some channels prepared for you to get BTP accounts:
-
If you are an SAP employee, access to grant the service entitlements to your Global Account.
-
If you are a partner or start-up, read to apply for BTP TDD (Test, Demonstration and Development) license account.
-
If you are a potential customer, please reach sales to apply an Enterprise Trial according to .
-
If you are a personal developer, start by reading to get a global free trial account.
Scenario
Persona: SaaS Application Provider
Let’s assume you are a SaaS Application provider, for example: Provider: TIA
. Provider: TIA
would like to provide an application which displays the logged in user’s name and customer’s tenant related information, shown as below:
Final project with multitenancy can be found: .
Persona: Customer
A consumer can subscribe to the application through the SAP BTP Account Cockpit.
Steps
-
Create and Configure the Approuter Application
-
Create and Configure Authentication and Authorization with XSUAA
-
Implement Subscription callbacks API
-
Register the Multitenant Application to the SAP SaaS Provisioning Service
-
Deploy the Multitenant Application to the Provider Subaccount
-
Subscribe SaaS Application by a Consumer
Step 1: Create and Configure the Approuter Application
Each multitenant application has to deploy its own application router, and the application router handles requests of all tenants to the application. The application router is able to determine the tenant identifier out of the URL and then forwards the authentication request to the tenant User Account and Authentication (UAA) service and the related identity zone.
For general instructions, see .
Create folder cf_multitenant_approuter_app
under the root directory.
mkdir cf_multitenant_approuter_app cd cf_multitenant_approuter_app
Under the folder cf_multitenant_approuter_app
, create a file package.json
with the following content:
{ "name": "cf_multitenant_approuter_app", "dependencies": { "@sap/xsenv": "^3", "@sap/approuter": "^8" }, "scripts": { "start": "node node_modules/@sap/approuter/approuter.js" } }
Then we should configure the routes in the application router security descriptor file (xs-app.json) so that application requests are forwarded to the multitenant application destination. Under the folder cf_multitenant_approuter_app
, create a file xs-app.json
with the following content:
{ "authenticationMethod": "none", "routes": [{ "source": "/", "target": "/", "destination": "dest_cf_multitenant_node_app" }] }
Update the backend NodeJS app in the mta.yaml
file to provide a destination to approuter app:
provides: - name: dest_cf_multitenant_node_app properties: node_app_url: '${default-url}'
Add the approuter app into the mta.yaml
file:
- name: cf_multitenant_approuter_app type: nodejs path: cf_multitenant_approuter_app parameters: disk-quota: 256M memory: 256M host: ${org}-cf-multitenant-approuter-app provides: - name: Router_api properties: url: ${default-url} application: ${app-name} requires: - name: dest_cf_multitenant_node_app group: destinations properties: name: dest_cf_multitenant_node_app url: '~{node_app_url}' forwardAuthToken: true
Step 2: Create and Configure Authentication and Authorization with XSUAA
To use a multitenant application router, you must have a shared UAA service and the version of the application router has to be greater than 2.3.1.
For general instructions, see .
Create and configure an XSUAA instance in the mta.yaml
(Also, we can create a security descriptor file xs-security.json
in JSON format that specifies the functional authorization scopes for the application instead):
-
Define the application provider tenant as a shared tenant
-
Provide access to the SAP SaaS Provisioning service (technical name: saas-registry) for calling callbacks and getting the dependencies API by granting scopes:
resources: - name: uaa_cf_multitenant type: org.cloudfoundry.managed-service requires: - name: Router_api properties: XSAPPNAME: ${xsuaa-app} parameters: #path: ./xs-security.json service-plan: application service: xsuaa shared: true xsuaa-app: ${space}-~{Router_api/application} config: xsappname: ${xsuaa-app} ### tenant-mode tenant-mode: shared description: Security profile of called application scopes: - name: "$XSAPPNAME.Callback" description: With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called. grant-as-authority-to-apps: - "$XSAPPNAME(application,sap-provisioning,tenant-onboarding)" oauth2-configuration: redirect-uris: - "http*://*.${default-domain}/**"
Add this instance into both the apps in the mta.yaml
:
requires: - name: uaa_cf_multitenant
Update the xs-app.json
file:
{ "authenticationMethod": "route", "routes": [{ "source": "/", "target": "/", "destination": "dest_cf_multitenant_node_app", "authenticationType": "xsuaa" }] }
Add libraries for enabling authentication in the cf_multitenant_node_app/app.js
file:
//**************************** Libraries for enabling authentication ***************************** var passport = require('passport'); var xsenv = require('@sap/xsenv'); var JWTStrategy = require('@sap/xssec').JWTStrategy; //************************************************************************************************
Enabling authorization in the cf_multitenant_node_app/app.js
file:
//*********************************** Enabling authorization *********************************** var services = xsenv.getServices({ uaa: { tag: 'xsuaa' } }); //Get the XSUAA service passport.use(new JWTStrategy(services.uaa)); app.use(passport.initialize()); app.use(passport.authenticate('JWT', { session: false })); //Authenticate using JWT strategy //************************************************************************************************
The application router must determine the tenant-specific subdomain for the UAA that in turn determines the identity zone, used for authentication. This determination is done by using a regular expression defined in the environment variable TENANT_HOST_PATTERN
.
More details: https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/5310fc31caad4707be9126377e144627.html?locale=en-US
- name: cf_multitenant_approuter_app ... properties: TENANT_HOST_PATTERN: "^(.*)-cf-multitenant-approuter-app.${default-domain}"
Now, if you try to deploy the MTA application, the backend application would return your tenant information accordingly.
Step 3: Implement Subscription callbacks API
Under the routes/index.js
file implements two APIs:
//******************************** API Callbacks for multitenancy ******************************** /** * Request Method Type - PUT * When a consumer subscribes to this application, SaaS Provisioning invokes this API. * We return the SaaS application url for the subscribing tenant. * This URL is unique per tenant and each tenant can access the application only through it's URL. */ router.put('/callback/v1.0/tenants/*', function(req, res) { var consumerSubdomain = req.body.subscribedSubdomain; var tenantAppURL = "https:\/\/" + consumerSubdomain + "-cf-multitenant-approuter-app." + "<custom-domain>"; res.status(200).send(tenantAppURL); }); /** * Request Method Type - DELETE * When a consumer unsubscribes this application, SaaS Provisioning invokes this API. * We delete the consumer entry in the SaaS Provisioning service. */ router.delete('/callback/v1.0/tenants/*', function(req, res) { console.log(req.body); res.status(200).send("deleted"); }); //************************************************************************************************
Replace the <custom-domain> with your custom domain for your multitenant application.
Step 4: Register the Multitenant Application to the SAP SaaS Provisioning Service
resources: ... - name: reg_cf_multitenant type: org.cloudfoundry.managed-service requires: - name: uaa_cf_multitenant parameters: service: saas-registry service-plan: application config: xsappname: ~{uaa_cf_multitenant/XSAPPNAME} appName: cloud-cf-multitenant-saas-provisioning-sample-hands-on displayName: Multitenancy Sample in Cloud Foundry description: 'A NodeJS application to show how to use the SaaS registry to build a multi-tenant application on BTP Cloud Foundry Runtime' category: 'Provider: TIA' appUrls: onSubscription: https://${org}-cf-multitenant-node-app.${default-domain}/callback/v1.0/tenants/{tenantId} onSubscriptionAsync: false onUnSubscriptionAsync: false # callbackTimeoutMillis: 1200000
Specify the following parameters:
Parameters | Description |
---|---|
xsappname | The xsappname configured in the security descriptor file used to create the XSUAA instance (see ). |
getDependencies | (Optional) Any URL that the application exposes for GET dependencies. If the application doesn’t have dependencies and the callback isn’t implemented, it shouldn’t be declared.NoteThe JSON response of the callback must be encoded as either UTF8, UTF16, or UTF32, otherwise an error is returned. |
onSubscription | Any URL that the application exposes via PUT and DELETE subscription. It must end with /{tenantId}. The tenant for the subscription is passed to this callback as a path parameter. You must keep {tenantId} as a parameter in the URL so that it’s replaced at real time with the tenant calling the subscription. This callback URL is called when a subscription between a multitenant application and a consumer tenant is created (PUT) and when the subscription is removed (DELETE). |
displayName | (Optional) The display name of the application when viewed in the cockpit. For example, in the application’s tile. If left empty, takes the application’s technical name. |
description | (Optional) The description of the application when viewed in the cockpit. For example, in the application’s tile. If left empty, takes the application’s display name. |
category | (Optional) The category to which the application is grouped in the Subscriptions page in the cockpit. If left empty, gets assigned to the default category. |
onSubscriptionAsync | Whether the subscription callback is asynchronous.If set to true, callbackTimeoutMillis is mandatory. |
callbackTimeoutMillis | The number of milliseconds the SAP SaaS Provisioning service waits for the application’s subscription asynchronous callback to execute, before it changes the subscription status to FAILED. |
allowContextUpdates | Whether to send updates about the changes in contextual data for the service instance.For example, when a subaccount with which the instance is associated is moved to a different global account.Defaut value is false. |
Bind this instance to your NodeJS application, update your mta.yaml
file:
modules: - name: cf_multitenant_node_app ... requires: - name: uaa_cf_multitenant - name: reg_cf_multitenant ...
Step 5: Deploy the Multitenant Application to the Provider Subaccount
You can build with the Cloud MTA Build Tool and Cloud Foundry CLI.
Download the Cloud MTA Build Tool: https://sap.github.io/cloud-mta-build-tool/download/
Install the Cloud MTA Build Tool:
npm install -g mbt
Navigate to the project root folder, and build your MTA project:
mbt build -p=cf
Install CF plugin to deploy MTA applications:
cf install-plugin multiapps
Make sure you are logged into a CF org/space:
cf login -a <api-endpoint> -o <org> -s <space>
In China you probably need to export an environment variable before you run “cf deploy” command.
export DEPLOY_SERVICE_URL=deploy-service.cfapps.<landscape-domain>
New variable name:
MULTIAPPS_CONTROLLER_URL
For instance:
export DEPLOY_SERVICE_URL=deploy-service.cfapps.cn40.platform.sapcloud.cn
Deploy with mtar file:
cf deploy mta_archives/cloud-cf-multitenant-saas-provisioning-sample-hands-on_1.0.0.mtar -f
More details: ,
Step 6: Subscribe SaaS Application by a Consumer
Now, a consumer can subscribe to the application through the SAP BTP Account Cockpit.
Switch to another subaccount under the same Global Account with the multitenant application provider subaccount, you can see and subscribe to the multitenant application.
Create an instance for the SaaS Application:
Click on Create
button:
Once it is subscribed, you can try to access it by clicking on the Go to Application
button:
You will get a 404 error that says requested route does not exist:
In the last step, the SaaS Application Provider needs to add a new route to ensure that a consumer’s request goes to the app router.
cf map-route cf_multitenant_approuter_app exercise.sap-samples.cn40.apps.platform.sapcloud.cn --hostname trial3-tia-cf-multitenant-approuter-app
Try to clean cache and access it again:
Deploying the application again would remove all the routes required by your customers. To keep all the routes as it is, adds one parameter into your approuter application:
modules: - name: cf_multitenant_approuter_app ... parameters: ... keep-existing: routes: true
Try to deploy it again.
thanks Tia.
do you have an example / reference document for multi tenancy , where each tenant has its own HDI container ?
( https://blogs.sap.com/2022/08/27/fundamentals-of-multitenancy-in-sap-btp/ )
I am looking to implement 2 & 3 here - Perhaps getDependencies() to be implemented ? ( but how )