Skip to Content
Technical Articles

Test your CAP nodejs applications with authentication locally

Recently we started our first CAP nodejs project.
Initially we used the SAP Web IDE, but we ran into the problem that we were not able to test our service with authentication quickly. We always had to deploy the service first, and that was taking a lot of time.

So we decided to move our developments locally and start using visual studio code for CAP developments. To test our service we want to use our data, destinations and authorizations from our Cloud foundry services.

In this blog post I will describe how you can use the XSUAA service on cloud foundry to be able to use authentication in your local environment.

Prerequisites

To be able to test the things I’ll describe here you have to install some tools …

cloud foundry cli

This command line tool is able to get information from your cloud foundry account.
Find download and installation information here:

https://docs.cloudfoundry.org/cf-cli/install-go-cli.html

SAP Cloud MTA Build Tool

This command-line tool builds a multitarget application archive file from the artifacts of an MTA project.

Because this tool is using makefile, Windows user should first install Chocolatey and GNU Make.

https://chocolatey.org/install

https://chocolatey.org/packages/make

When make is installed, you are able to install the MTA Build tool:

npm install -g mbt

More information here:

https://sap.github.io/cloud-mta-build-tool/

Example project

Through this blog post you can use my example project on github.
You can just deploy this example on your trial account and test it.

https://github.com/jowavp/SAP-CAPM-Nodejs-Authorisation-example

Clone the project to your PC and try to deploy it to your cloud foundry account.

git clone https://github.com/jowavp/SAP-CAPM-Nodejs-Authorisation-example
cd SAP-CAPM-Nodejs-Authorisation-example\

in your command you have to log on to your cloud foundry account. Just enter “cf login” in your shell and provide the correct information.

cf login
API endpoint: https://api.cf.eu10.hana.ondemand.com

Email> joachim.vanpraet@****.com

Password>
Authenticating...
OK

Select an org (or press enter to skip):
1. S0003564161trial_trial

Org> 1
Targeted org S0003564161trial_trial

Targeted space dev

API endpoint:   https://api.cf.eu10.hana.ondemand.com (API version: 2.141.0)
User:           joachim.vanpraet@****.com
Org:            S0003564161trial_trial
Space:          dev

then you can deploy the project! just run

npm run deploy

this command will build the MTA archive and start deployment on cloud foundry. Time to take a coffee, this can take some time 🙂

If the project is deployed, you can test it by opening the url of the approuter module in our project.
To get this url, just run ‘cf apps’

> cf apps
Getting apps in org S0003564161trial_trial / space dev as joachim.vanpraet@****.com...
OK

name                          requested state   instances   memory   disk   urls
CAPMAuthorisation-approuter   started           1/1         256M     256M   s0003564161trial-trial-dev-capmauthorisation-approuter.cfapps.eu10.hana.ondemand.com
CAPMAuthorisation-srv         started           1/1         512M     256M   s0003564161trial-trial-dev-capmauthorisation-srv.cfapps.eu10.hana.ondemand.com

you’ll see a table of all applications running on your cloud foundry environment. The last column is the url, if you open this url in a brower, the approuter will route you to the service.

This should be the response:

{
  "@odata.context": "$metadata#User(username)",
  "@odata.metadataEtag": "W/\"V7/t5WepB4qxHRqSBCe3fnSl+gQ4dVwCnhtBLUGyukQ=\"",
  "value": [
    {
      "username": "joachim.vanpraet@****.com"
    }
  ]
}

Running locally

Let’s get this thing running on our local environment. To know which user is logged on we need the authentication token (in cloud foundry this is the JWT Token) injected in the service. Injecting this token is done by the approuter. The approuter will get the token from the uaa service and add it to the the Authorization header in the request to the service.

The secret for achieving this with minimal effort is in using default-services.json and default-env.json files. These files are included in the .gitignore file because they contain secrets you don’t want to share with the world.

The Service

The source files of the service are located in the srv folder. When you open the service.cds you can see the following:

 

@impl:'service.js'
@cds.query.limit: 100
service CapmAuthUserService @(requires:'authenticated-user'){
  entity User {
    key username   : String;
  }    
}

Important piece of code here is the @(requires:’authenticated-user’) this will only allow authenticated users to access theservice.

open the shell in the root directory of the project and install all npm package

npm install

After the installation of the nod modules, we start the service with the following command:

npm run start:local

This will start the service locally on https://localhost:4004

If we follow this link, we will get a 403 response ‘Forbidden:

{"error":{"message":"Forbidden"}}

This is due to the fact that we’ve annotated the CDS service with @(requires:’authenticated-user’)

Keep this service running, don’t close your shell.

Open a new shell window for the following steps where you will inject a JWT token into the service.
As mentioned earlier, this is done by the approuter.

Approuter

The approuter is configured in the ‘approuter’ folder in the project. xs-app.json

{
	"welcomeFile": "/capm-auth-user",
	"authenticationMethod": "route",
	"logout": {
		"logoutEndpoint": "/do/logout"
	},
	"routes": [{
		"source": "^/(.*)$",
		"target": "$1",
		"authenticationType": "xsuaa",
		"scope": "$XSAPPNAME.user",
		"destination": "srv_api",
		"csrfProtection": false
	}]
}

We’ve set authenticationMethod to “route” so we decide in each route which authentication type we want to use. There is only one route that will proxy all requests to the srv_api destination and uses xsuaa for the authentication.

Let’s first check the mta.yaml file for this module in the root folder of the project.

  - name: CAPMAuthorisation-approuter
    type: approuter.nodejs
    path: approuter
    parameters:
      disk-quota: 256M
      memory: 256M
    requires:
      - name: CAPMAuthorisation-uaa
      - name: srv_api
        group: destinations
        properties:
          forwardAuthToken: true
          name: srv_api
          strictSSL: false
          url: '~{url}'

In Cloud Foundry this approuter module requires 2 services:

  • The CAPMAuthorisation-uaa service (which is the uaa service)
  • The srv_api destination (which is provided by our CAPMAuthorisation-srv module)

In the srv_api destination, we set forwardAuthToken to true. This will forward the JWT token from the approuter to the destination.

If we want to run this approuter module locally, we must install the node modules for this module. Go into the approuter directry

cd approuter

Install the node modules

npm install

Run the approuter

npm run start

This will fail, because the approuter is not able to find the destination srv_api. When you deploy this app to cloud foundry, the approuter will find the srv_api destination because it is defined in the mta.yaml file.

C:\Users\vanprjo\git\capm-nodejs-authorisations\approuter\node_modules\@sap\approuter\lib\utils\JsonValidator.js:30    
throw new VError('%s%s: %s',

VError: xs-app.json/routes/0: Format validation failed (Route references unknown destination "srv_api")

We have to define this destination for our local environment.
create a default-env.json file in the approuter folder.

{
	"destinations": [
		{
			"name": "srv_api",
			"url": "http://localhost:4004",
			"forwardAuthToken": true,
			"strictSSL": false
		}
	]
}

This will create the destination like we did in the mta.yaml file, but this will point to http://localhost:4004. This is the address where our service is running.

Let’s try to start the approuter again …

npm run start

This will again fail because you need a uaa service to authenticate yourself.

C:\git\capm-nodejs-authorisations\approuter\node_modules\@sap\approuter\lib\configuration.js:64
throw new Error('No UAA service found');
Error: No UAA service found

We can reuse the authentication service of our CF account. Let’s first search for the credentials we need for this service.

run cf apps

Get the environment variables for the approuter app

cf env CAPMAuthorisation-approuter

We will only focus on the parameters in System-Provided. There we see a service xsuaa. Copy the values in credentials.

Create a default-services.json file in the approuter folder where we will set the credentials to the UAA service.

This is the content of default-services.json.

{
    "uaa": {
        "apiurl": "https://api.authentication.eu10.hana.ondemand.com",
        "clientid": "<your_clientid>",
        "clientsecret": "<your_clientsecret>",
        "identityzone": "<your_identityzone>",
        "identityzoneid": "<your_identityzoneid>",
        "sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
        "tenantid": "<your_tenantid>",
        "tenantmode": "dedicated",
        "uaadomain": "authentication.eu10.hana.ondemand.com",
        "url": "https://<your_account>.authentication.eu10.hana.ondemand.com",
        "verificationkey": "-----BEGIN PUBLIC KEY-----<public key>-----END PUBLIC KEY-----",
        "xsappname": "<your_appname>"
    }
}

We also need to copy this file to the root directory of our project because in the service, we need to validate the JWT token. But I found out that in the @sap/cds library they are looking for xsuaa service, so we have to rename uaa to xsuaa … (only rename the service in the default-services.json file in the root directory of the project)

{
    "xsuaa": {
        "apiurl": "https://api.authentication.eu10.hana.ondemand.com",
        "clientid": "<your_clientid>",
        "clientsecret": "<your_clientsecret>",
        "identityzone": "<your_identityzone>",
        "identityzoneid": "<your_identityzoneid>",
        "sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
        "tenantid": "<your_tenantid>",
        "tenantmode": "dedicated",
        "uaadomain": "authentication.eu10.hana.ondemand.com",
        "url": "https://<your_account>.authentication.eu10.hana.ondemand.com",
        "verificationkey": "-----BEGIN PUBLIC KEY-----<public key>-----END PUBLIC KEY-----",
        "xsappname": "<your_appname>"
    }
}

Let’s try to start the approuter again …

npm run start

The approuter is running on port 5000

Note:

It is important to install node modules “passpor”t and “@sap/xssec” in the root folder of the project. Otherwise the injected token will be ignored in the CDS service.

The result

let’s go to http://localhost:5000

TADAA …

Now you have the logon screen of your account.
Enter your credentials and when you navigate to the entity User you should be able to see the current userid.

As you can see running nodejs based cloud foundry application on your own environment is very easy. The new SAP Application studio (which is available as beta feature at the moment) should also support this approach for developing and testing CAP nodejs application.

kr,

Joachim

11 Comments
You must be Logged on to comment or reply to a post.
  • Hi Joachim,

    thanks for sharing this blog.

    I followed your instructions and I found a couple of issues.

    First of all in order to make my Approuter working I had to use “UAA” instead of “XSUAA” in the default-services.json file.

    The second issue is that after I’ve made the Approuter starting, assigned the roles

    CA_Admin and CA_User to my user and logged into the Approuter I’m still receiving the error message “Unauthorized” when I go to the URL http://localhost:5000/capm-auth-user/$metadata. In the log, before the JWT printout, I see this:

    #2.0#2019 11 04 11:37:56:141#+01:00#INFO#/Auth/OAuth2#####k2kaji20####0RrAUzT8jnylcEUZA6SxLUuHQpQls_lc######k2kaji20#PLAIN##sending page with client-side redirect to https://sd-iendh-dev-openportal.authentication.eu10.hana.ondemand.com/oauth/authorize?response_type=code&client_id=sb-CAPMAuthorisation!t12388&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Flogin%2Fcallback#

    #2.0#2019 11 04 11:37:56:141#+01:00#INFO#/Auth/OAuth2#####k2kaji20####0RrAUzT8jnylcEUZA6SxLUuHQpQls_lc######k2kaji20#PLAIN##x-forwarded-path header: undefined#

    { jti: ‘c6830ccb7a9f4d2f82bebc591bba8280’,

     

    Have you any idea about what the issue could be?

    Thanks!

    Simmaco

     

    • Hi Simmaco,

      Have you created 2 default-services.json files in this project?

      We are running 2 applications locally here. One cds service and one approuter. Both of the applications need a reference to the uaa service. This reference needs to be configured for each application in the default-services.json file. So, you need to create 2 default-services.json files.

      The first one in the directory of the approuter, as you mentioned, you have to give it the key uaa by default. If you want to use another key, you can define this in an environment variable UAA_SERVICE_NAME (you can define this in default-env.json).

      The second default-services.json file is in the root folder of your application (same directory as where your mta.yaml file is located). And there you need to use the key xsuaa for it.

      You don’t need to assign any roles to your user in this example. We only verify if the user is an authenticated user with @(requires:’authenticated-user’).

      https://cap.cloud.sap/docs/guides/authorization#restrictions

      also check if you have installed node_modules @sap/xssec and passport, but normally you should have them because I’ve added them to the dependencies in package.json.

      And the last thing I can think of is that we need to forward the JWT token from the approuter to the service. And this is configured in the default-env.json file in the approuter directory (“forwardAuthToken”: true).

      I hope one of these solutions fixes your problem. If not, please let me know!

      kr,

      Joachim

       

       

       

    • Hi Simmaco,

      Do you have the default-env.json in the root of the project? I was having this same issue the default-env.json was wrongly in /srv. Then I moved it one level up, in other words the root dir and as it is stated in the blog post works fine.

      Best,

      Cipriano

  • I have tried this and getting the local app router to redirect to the CF XSUAA login was straightforward.

    However, it looks like the local service doesn’t accept the injected JWT token. No matter what I try, the locally running service responds with 403 and the CDS watch log shows

    GET /key
    {
      data: 'Unsuccessful login attempt',
      user: '::1',
      ip: '::1',
      uuid: '3378654DEE52DD75CE4AE29E65C6DC46',
      time: '2020-03-04T11:56:52.919Z'
    }

    Deployed on SCP the same project works fine, but for some reason it does not accept the authenticated user locally. Any ideas what to look for? Is there a way to debug the service to see what it receives in the JWT token?

    I also have a question on you xs-app.json. Here it looks like you configured the scope. Is this necessary? This would mean I would have to hard code any scope I want the approuter to forward to the service in xs-app.json? I am asking because I don’t have this configuration and authorization works fine (on SCP/CF).

     

  • just a tip:

    if you’re facing the error ‘deploy is not a registered command’ after running ‘npm run deploy’ or ‘cf deploy’, is because you haven’t installed multiapps plugin on your local environment yet. To do so, just run the command below:

    cf install-plugin multiapps