You need your own application router that connects your service to the centrally provided "user account and authentication (UAA) service". Technically this means that you need to deploy an approuter as part of your application that manages the user authentication for you.
The approuter has these main functions:
- Handles authentication for all apps of the application
- Serves static resources
- Performs route mapping (URL mapping)
- In case of multi tenancy it derives the tenant information from the url and provides it to the XSUAA to redirect the authentication request to the tenant specific identity provider.
Continue with your solution of the last exercise. If this does not work, you can checkout the branch origin/solution-19-Transfer-CorrelationID.
Ensure that Node.JS including its NPM Packager Manager is installed:
node --version
npm --version
- Inside your
cc-bulletinboard-ads
directory create a directory with the namesrc/main/approuter
(right-click on the project, then select "New" - "Folder"). - In there create a file named
.npmrc
with the following content specifying the registry:
@sap:registry=https://npm.sap.com
- Furthermore create a file named
package.json
with the following content (similar topom.xml
):
{
"name": "approuter",
"dependencies": {
"@sap/approuter": "3.0.1"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
},
"engines": {
"node": "6"
}
}
Like we are doing it for our bulletinboard-ads Java application, we also recommend to build the approuter Node application once (locally or later triggered as part of the Continuous Delivery build) before deploying it to Cloud Foundry. Therefore we use the NPM Packager Manager to download the packages (node_modules
) as specified in the package.json
.
In order to avoid Eclipse crashes / getting slow while parsing packages downloaded via NPM in the next step, ensure that there is a resource filter defined for the node_modules
directory as visualized in the screenshot (project properties):
Now execute in the terminal (within directory src/main/approuter
):
npm install
With this the node modules are downloaded by the NPM package manager from the https://npm.sap.com
SAP external NPM repository (aka registry) and are copied into directory src/main/approuter/node_modules/@sap/approuter
.
As the approuter is a Node.JS application, it needs to be added into the manifest.yml
as another sub element of the applications
element, just like bulletinboard-ads
:
- name: approuter
host: approuter-<<your user id>>
path: src/main/approuter
buildpack: https://github.com/cloudfoundry/nodejs-buildpack.git#v1.6.10
memory: 128M
env:
XSAPPNAME: bulletinboard-<<your user id>>
TENANT_HOST_PATTERN: "^(.*)-approuter-<<your user id>>.cfapps.<<region>>.hana.ondemand.com"
destinations: >
[
{"name":"ads-destination",
"url":"https://bulletinboard-ads-<<your user id>>.cfapps.<<region>>.hana.ondemand.com",
"forwardAuthToken": true}
]
services:
- applogs-bulletinboard
- uaa-bulletinboard
Furthermore you need to specify the host
of your bulletinboard-ads
application as well. For example: host: bulletinboard-ads-<<your user id>>
. Reason: As the manifest.yml
contains now multiple applications you are not longer able to specify the host using the command line flag -n
. The references to <<region>>
needs to be replaced with eu10 or us10 depending on the trial environment where you have registered. For more details, please refer the documentation
Note: Even though the
approuter
is not "stateless" (as it maps theSessionID
to theJWT token
) the amount ofapprouter
instances depends on the load on the business application. This is possible as Session stickiness is implemented by Cloud Foundry with theVCAPID
header. Using the same header value in the following requests causes the Cloud Foundry router to route those requests to the same (approuter
) application instance. BUT: applications MUST NOT rely on being called by the sameapprouter
instance during a session. Theapprouter
instance could change if the old instance dies and a new instance is created during the recovery procedure.
Now create in the src/main/approuter
directory a file named xs-app.json
with the following content:
{
"welcomeFile": "index.html",
"routes": [{
"source": "^/ads",
"target": "/",
"destination": "ads-destination"
}]
}
If you like to provide a "welcome file" then you need also to add an index.html
file, that must be created within the src/main/approuter/resources
directory. Be aware that the "ads-destination" destination is already specified as system environment variable in the manifest.yml
.
Note: The
ads-destination
used in this file is a logical destination that is mapped to theads-destination
defined in themanifest.yml
. This is because the 'real URL' is defined at deploy time (it may even be a 'random route') and thexs-app.json
is concerned only with the endpoints relative to the app URL.
In this step we create an XSUAA service instance that is able to serve requests from multiple customers, so called tenants.
- Create an XSUAA service instance with name
uaa-bulletinboard
and replacexsappname
placeholder accordingly.
Note: The following statement works on Linux/Mac only (get other examples withcf cs -h
).
$ cf create-service xsuaa application uaa-bulletinboard -c '{"xsappname":"bulletinboard-<Your user id>"}'
- Deploy the applications with:
$ cf push # host names are already specified in the manifest
- Then have a look at the XS UAA service connection information that is part of the
VCAP_SERVICES
environment variable and try to find theidentityzone
.
$ cf env approuter
Important Note: The value of
identityzone
matches the value ofsubdomain
name of the subaccount and represents the name of the tenant for the next steps.
- Enter
cf routes
to see which routes are already mapped to your applications. Every tenant consuming your application needs his own route, prefixed with tenant (e.g.p20001234trial
) and needs to be created with the following command:
$ cf map-route approuter cfapps.<<region>>.hana.ondemand.com -n <<your tenant>>-approuter-<<your user id>>
Observe how the authentication works:
- Get the url of the approuter via
cf apps
. - Then enter the approuter URL e.g.
https://<<your tenant>>-approuter-<<your user id>>.cfapps.<<region>>.hana.ondemand.com
in the browser. This should redirect you to the XS-UAA Logon Screen. Note that you've configured your approuter on how to derive the tenant from the URL according to theTENANT_HOST_PATTERN
that you've provided as part of themanifest.yml
. - You will be redirected to SAP User ID Service (https://accounts.sap.com), login with your SAP email address and domain password.
- After successful login to you will get redirected to the welcome page if you've defined one.
Observe the route / path mappings:
- Test what happens if you enter
ads
orads/health
as path and again check out the welcome page. - Test what happens if you enter
ads/api/v1/ads/1
So, what have you achieved right now? You have made your application accessible via the application router, that authenticates the user before they get redirected to the application.
BUT your application is still not secure!
- Any user can access any service endpoint with an invalid or even without security token, without any permissions. That needs to be prevented and we will do this in the next exercise.
-
Application router also implements Cross-Site Request Forgery (CSRF) protection. A modification request (PUT, POST, DELETE, etc.) is rejected unless the request contains a valid x-csrf-token header. Clients can fetch this token after successful authentication/authorization. It is enough to fetch this token only once per session. The x-csrf-token can be obtained with HTTP header
x-csrf-token: fetch
. -
Application router can be configured to initiate a central logout after a specified time of inactivity (no requests have been sent to the application router). Assign the time of inactivity in minutes to the environment variable
SESSION_TIMEOUT
in yourmanifest.yml
file. Ensure that the timeout configuration for application router and XSUAA are identical for all routes with authentication typexsuaa
-
Each instance of application router holds it´s own mappings of JWT-Tokens to jSessionIDs. The mappings are neither shared across multiple instances of application router nor are the mappings persisted in a common persistency. Application router uses Cloud Foundry session stickiness (VCAP_ID) to ensure that requests belonging to the same session are routed via the same application router instance - this means that the user will need to re-authenticate if an application router instance of a particular session goes down and is recovered by a new application router instance.
-
© 2018 SAP SE