EDUCAÇÃO E TECNOLOGIA

Multitenancy – Develop and Register Multitenant Application to the SAP SaaS Provisioning Service on the SAP BTP: Hands-on Tutorial on Cloud Foundry

In this post, you can get a step-by-step tutorial which helps you develop a NodeJS application from the scratch to show how to use the SaaS registry to build a multi-tenant application on BTP Cloud Foundry Runtime.

For more general descriptions on how many steps it takes to do from a normal application to a multitenant application, you can read this blog.

And more posts will follow and provides other practical demo on Kyma runtime as well as local troubleshooting. Let’s stay tuned.

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: Express application generator. Or, you can git clone the skeleton project directly from: here.

    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 Development on BTP.

BTP account

Here are some channels prepared for you to get BTP accounts:

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: here.

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 Application Router.

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 SAP Authorization and Trust Management Service in the Cloud Foundry Environment.

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 SAP Authorization and Trust Management 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 Develop the Multitenant Application).
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: MultiApps CF CLI Plugin, Build and Deploy Multitarget Applications using Cloud MTA Build Tool and Cloud Foundry CLI

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.