Using the SAP Application Router with the SAP Cloud SDK
In this guide, we will show you how to use the SAP Application Router together with the SAP Cloud SDK. You will learn how to secure your application and configure multi-tenancy for principal propagation with an SAP Cloud SDK-based application example powered by nestJS.
SAP Application Router
When we want to enable multi-tenancy for our application, we use the SAP Application Router. The application router’s primary purpose is to be the single entry point of a microservice-based application and act as the application’s reverse proxy.
Its responsibilities consist of dispatching requests to backend microservices, authenticating users, and serving static content. The application router checks if a given request has a valid JSON Web Token (JWT) when accessing a target service. If the request contains a valid JWT, the application router forwards the request to the target service; if the request does not contain a valid JWT, the user must authenticate. As we can see in the diagram below, we use an Identity Provider (IdP) to authenticate, the request is redirected to an IdP where a user gets authenticated and then redirected back to the application router for passing further according to its desired destination.
sequenceDiagram
User->>Approuter: sending request
alt has no JWT
Approuter->>Identity Provider: redirecting
Identity Provider->>Identity Provider: authenticating
Identity Provider->>Identity Provider: granting JWT
Identity Provider->>Approuter: redirecting
else has JWT
Approuter->>Backend App: forwarding request
end
Application Router Setup
To deploy our application router in SAP BTP Cloud Foundry, we need to configure it first. Let's walk through the four files we need to use.
The xs-security.json
file defines the security and deployment options for an application.
With the below example, we enable the shared
tenant-mode for our xsuaa
instance, which we need for multi-tenancy.
{
"xsappname": "approuter-scaffold",
"tenant-mode": "shared"
}
In the xs-app.json
, we specify to which backend service a request is forwarded to, and how this request has to be authenticated.
We can optionally also specify a specific identityProvider
that is used for the authentication.
In the example below, we forward every request against the application router's /
route to the backend destination's /
route.
{
"welcomeFile": "index.html",
"routes": [
{
"source": "/",
"target": "/",
"destination": "approuter-scaffold"
}
]
}
In the package.json
we only have one dependency, the application router module.
{
"name": "approuter",
"dependencies": {
"@sap/approuter": "*"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
The manifest contains our application router, as well as environment variables which our application router needs for multi-tenancy.
As you see under env
, we specify the TENANT_HOST_PATTERN
and destinations
.
The destinations
are the destinations we use in our xs-app.json
where we forward requests to.
The TENANT_HOST_PATTERN
is a regular expression that describes how a tenant name should be retrieved from the host.
We also have to bind the xsuaa
which we configured with our xs-security.json
to the application router.
applications:
- name: approuter-scaffold-approuter
routes:
- route: approuter-scaffold-apps.cfapps.sap.hana.ondemand.com
path: .
memory: 128M
buildpacks:
- nodejs_buildpack
env:
TENANT_HOST_PATTERN: 'approuter-scaffold-(.*).cfapps.sap.hana.ondemand.com'
destinations: '[{"name":"approuter-scaffold","url":"approuter-scaffold.cfapps.sap.hana.ondemand.com","forwardAuthToken":true}]'
services:
- approuter-scaffold-xsuaa
Securing Your Application
To secure our application endpoints, we utilize the passport library. It lets us authenticate endpoints using a JSON web token.
Additionally, we use the xsenv
library to retrieve our xsuaa
credentials and the xssec
library's JWTStrategy
object for the middleware.
Below is a simple example, where we get the approuter-scaffold-xsuaa
which is bound to our application, use it in the JWTStrategy
, and then forward the middleware to the passport.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { JWTStrategy } from '@sap/xssec';
import { getServices } from '@sap/xsenv';
import * as passport from 'passport';
const xsuaa = getServices({
xsuaa: { name: 'approuter-scaffold-xsuaa' }
}).xsuaa;
passport.use(new JWTStrategy(xsuaa));
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(passport.initialize());
app.use(passport.authenticate('JWT', { session: false }));
await app.listen(process.env.PORT || 3000);
}
bootstrap();
Enabling Principal Propagation
To enable principal propagation with this setup, we must forward the request to our endpoints.
First, we forward the request in our app.controller.ts
to our principal propagation endpoint.
@Get('principal-business-partner')
getPrincipalBusinessPartner(
@Req() request: Request,
): Promise<BusinessPartner[]> {
return this.principalBusinessPartnerService.getFiveBusinessPartners(
request,
);
}
Then, we simply use the SAP Cloud SDK's retrieveJwt
function to extract the JWT from our request, and forward it to the execute
method.
Below is an example using the BusinessPartnerService
, where we retrieve the top five business partners.
import { Injectable } from '@nestjs/common';
import { BusinessPartner } from '@sap/cloud-sdk-vdm-business-partner-service';
import { retrieveJwt } from '@sap-cloud-sdk/core';
import { Request } from 'express';
@Injectable()
export class PrincipalBusinessPartnerService {
async getFiveBusinessPartners(request: Request): Promise<BusinessPartner[]> {
return BusinessPartner.requestBuilder()
.getAll()
.top(5)
.execute({
destinationName: 'MY-DESTINATION',
jwt: retrieveJwt(request)
});
}
}