Skip to Content

Create an Application with Cloud Foundry Node.js Buildpack

Create a simple Node.js application and enable services for it, by using the Cloud Foundry Node.js buildpack and Cloud Foundry Command Line Interface (cf CLI).
You will learn
  • How to create a simple “Hello World” application in Node.js
  • How to create an application router for it
  • How to run authentication and authorization checks via the Authorization and Trust Management (XSUAA) service
JoysieGergana TsakovaNovember 11, 2024
Created by
slavipande
July 14, 2023
Contributors
Joysie
slavipande

Prerequisites

  • You have a trial or a productive account for SAP Business Technology Platform (SAP BTP). If you don’t have such yet, you can create one so you can try out services for free.
  • You have created a subaccount and a space on the SAP BTP, Cloud Foundry environment.
  • cf CLI is installed locally.
  • Node.js and npm are installed locally. Make sure you have the latest Node.js version. In this tutorial, we use Node version 20.x and npm version 10.x.
  • You have installed an integrated development environment, for example Visual Studio Code.

This tutorial will guide you through creating and setting up a simple Node.js application in cf CLI. You will start by building and deploying a web application that returns simple data – a Hello World! message, and then invoking this app through a web microservice (application router). Finally, you will set authentication checks and authorization roles to properly access and manage your web application.

  • Step 1

    First, you need to connect to the SAP BTP, Cloud Foundry environment with your trial or enterprise (productive) subaccount. Your Cloud Foundry URL depends on the region where the API endpoint belongs to. To find out which one is yours, see: Regions and API Endpoints Available for the CF Environment

    In this tutorial, we use eu20 as an example.

    1. Open a command-line console.

    2. Set the Cloud Foundry API endpoint for your subaccount. Run the following command (using your actual region URL):

      Bash/Shell
      Copy
      cf api https://api.cf.eu20.hana.ondemand.com
      
    3. Log on to the SAP BTP, Cloud Foundry environment:
      Bash/Shell
      Copy
      cf login
      
    4. When prompted, enter your user credentials. These are the email and password you have used to register your trial or productive SAP BTP account.

      IMPORTANT: If the authentication fails, even though you’ve entered correct credentials, try logging in via single sign-on.

    5. Choose the org name and space where you want to create your application.

      If you’re using a trial account, you don’t need to choose anything. You can use only one org name, and your default space is dev.

    RESULT

    Details about your personal SAP BTP subaccount are displayed (API endpoint, user, organization, space).

  • Step 2

    You’re going to create a simple Node.js application.

    1. In your local file system, create a new directory (folder). For example: node-tutorial

    2. From your Visual Studio Code, open the node-tutorial folder.

    3. In this folder, create a file manifest.yml with the following content:

      YAML
      Copy
      ---
      applications:
      - name: myapp
        random-route: true
        path: myapp
        memory: 128M
        buildpacks:
        - nodejs_buildpack
      

      The manifest.yml file represents the configuration describing your application and how it will be deployed to Cloud Foundry.

      IMPORTANT: Make sure you don’t have another application with the name myapp in your space! If you do, use a different name and adjust the whole tutorial according to it.

    4. In the node-tutorial folder, create a subfolder myapp.

    5. In the myapp directory, run:

      Bash/Shell
      Copy
      npm init
      

      Press Enter on every step. This process will walk you through creating a package.json file in the myapp folder.

    6. Then, still in the myapp directory, run:

      Bash/Shell
      Copy
      npm install express --save
      

      This operation adds the express package as a dependency in the package.json file.

      After the installation is completed, the content of package.json should look like this:

      JSON
      Copy
      {
        "name": "myapp",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1"
        },
        "author": "",
        "license": "ISC",
        "dependencies": {
          "express": "^4.19.2"
        }
      }
      
    7. Add engines to the package.json file and update the scripts section. Your package.json file should look like this:

      JSON
      Copy
      {
        "name": "myapp",
        "version": "1.0.0",
        "description": "My simple Node.js app",
        "main": "index.js",
        "engines": {
          "node": "20.x.x"
        },
        "scripts": {
          "start": "node start.js"
        },
        "author": "",
        "license": "ISC",
        "dependencies": {
          "express": "^4.19.2"
        }
      }
      
    8. In the myapp folder, create a file start.js with the following content:

      JavaScript
      Copy
      const express = require('express');
      const app = express();
      
      app.get('/', function (req, res) {
        res.send('Hello World!');
      });
      
      const port = process.env.PORT || 3000;
      app.listen(port, function () {
        console.log('myapp listening on port ' + port);
      });
      

      This code creates a simple web application returning a Hello World! message when requested. The express module represents the web server part of this application. Once these steps are completed, you can see that the express package has been installed in the node_modules folder.

    9. Deploy the application on Cloud Foundry. To do that, in the node-tutorial directory, run:

      Bash/Shell
      Copy
      cf push
      

      NOTE: Make sure you always run cf push in the directory where the manifest.yml file is located! In this case, that’s node-tutorial.

    10. When the staging and deployment steps are completed, the myapp application should be successfully started and its details displayed in the command console.

    11. Open a browser window and enter the generated URL of the myapp application (see routes).

      For example: https://myapp-purple-tiger.cfapps.eu20.hana.ondemand.com

    RESULT

    Your Node.js application is successfully deployed and running on the SAP BTP, Cloud Foundry environment. A Hello World! message is displayed in the browser.

    Which file contains information about the buildpack providing the runtime on which you deploy your application?

  • Step 3

    Authentication in the SAP BTP, Cloud Foundry environment is provided by the Authorization and Trust Management (XSUAA) service. In this example, OAuth 2.0 is used as the authentication mechanism. The simplest way to add authentication is to use the Node.js @sap/approuter package. To do that, a separate Node.js micro-service will be created, acting as an entry point for the application.

    1. In the node-tutorial folder, create an xs-security.json file for your application with the following content:
      JSON
      Copy
      {
        "xsappname" : "myapp",
        "tenant-mode" : "dedicated",
        "oauth2-configuration": {
          "redirect-uris": [
              "https://*.cfapps.eu20.hana.ondemand.com/**"
            ]
          }
      }
      

    > NOTE: Replace eu20 with the technical key of your actual SAP BTP region.

    1. Create an xsuaa service instance named nodeuaa with plan application. To do that, in the node-tutorial directory run:

      Bash/Shell
      Copy
      cf create-service xsuaa application nodeuaa -c xs-security.json
      
    2. Add the nodeuaa service in manifest.yml so the file looks like this:

      YAML
      Copy
      ---
      applications:
      - name: myapp
        random-route: true
        path: myapp
        memory: 128M
        buildpacks: 
        - nodejs_buildpack
        services:
        - nodeuaa
      

      The nodeuaa service instance will be bound to the myapp application during deployment.

    3. Now you need to create a microservice (the application router). To do that, go to the node-tutorial folder and create a subfolder web.

      IMPORTANT: Make sure you don’t have another application with the name web in your space! If you do, use a different name and adjust the rest of the tutorial according to it.

    4. In the web folder, create a subfolder resources. This folder will provide the business application’s static resources.

    5. In the resources folder, create an index.html file with the following content:

      HTML
      Copy
      <html>
      <head>
        <title>Node.js Tutorial</title>
      </head>
      <body>
        <h1>Node.js Tutorial</h1>
        <a href="/myapp/">My Node.js Application</a>
      </body>
      </html>
      

      This will be the start page of the myapp application.

    6. In the web directory, run:

      Bash/Shell
      Copy
      npm init
      

      Press Enter on every step. This process will walk you through creating a package.json file in the web folder.

    7. Now you need to create a directory web/node_modules/@sap and install an approuter package in it. To do that, in the web directory run:

      Bash/Shell
      Copy
      npm install @sap/approuter --save
      
    8. In the web folder, open the package.json file and replace the scripts section with the following:

      JSON
      Copy
      "scripts": {
          "start": "node node_modules/@sap/approuter/approuter.js"
      },
      
    9. Now you need to add the web application to your project and bind the XSUAA service instance (nodeuaa) to it. To do that, insert the following content at the end of your manifest.yml file.

      YAML
      Copy
      - name: web
        random-route: true
        path: web
        memory: 128M
        env:
          destinations: >
            [
              {
                "name":"myapp",
                "url":"https://myapp-purple-tiger.cfapps.eu20.hana.ondemand.com/",
                "forwardAuthToken": true
              }
            ]
        services:
        - nodeuaa
      

      NOTE: For the url value, enter your actual generated URL for the myapp application.

    10. In the web folder, create an xs-app.json file with the following content:

      JSON
      Copy
      {
        "routes": [
          {
            "source": "^/myapp/(.*)$",
            "target": "$1",
            "destination": "myapp"
          }
        ]
      }
      

      With this configuration, the incoming request is forwarded to the myapp application, configured as a destination. By default, every route requires OAuth authentication, so the requests to this path will require an authenticated user.

    11. In the myapp directory, run the following commands (one by one) to download packages @sap/xssec, @sap/xsenv, and passport:

      Bash/Shell
      Copy
      npm install @sap/xssec --save
      
      npm install @sap/xsenv --save
      
      npm install passport --save
      
    12. Verify that the request is authenticated. Check the JWT token in the request using the JWTStrategy provided by the @sap/xssec package. To do that, go to the myapp directory, and replace the content of the start.js file with the following:

      JavaScript
      Copy
      const express = require('express');
      const passport = require('passport');
      const xsenv = require('@sap/xsenv');
      const { createSecurityContext, requests, constants, TokenInfo, JWTStrategy } = require("@sap/xssec").v3;
      
      const app = express();
      
      const services = xsenv.getServices({ uaa:'nodeuaa' });
      
      passport.use(new JWTStrategy(services.uaa));
      
      app.use(passport.initialize());
      app.use(passport.authenticate('JWT', { session: false }));
      
      app.get('/', function (req, res, next) {
        res.send('Application user: ' + req.user.id);
      });
      
      const port = process.env.PORT || 3000;
      app.listen(port, function () {
        console.log('myapp listening on port ' + port);
      });
      

      IMPORTANT: Make sure your @sap/xssec is version 4.1.3 or higher. You can check this by running: npm list @sap/xssec.
      If it’s a lower version, run npm update and then check again.

    13. Go to the node-tutorial directory and run:

      Bash/Shell
      Copy
      cf push
      

      This command will update the myapp application and deploy the web application.

      What’s going on?

      At this point of the tutorial, the URL of the web application will be requested instead of the myapp URL. It will then forward the requests to the myapp application.

    14. When the staging and deployment steps are completed, the web application should be successfully started and its details displayed in the command console.

    15. Open a new browser tab or window and enter the generated URL of the web application.

      For example: https://web-happy-ladybug.cfapps.eu20.hana.ondemand.com

    16. Enter the credentials for your SAP BTP user.

      Both the myapp and web applications are bound to the same Authorization and Trust Management (XSUAA) service instance nodeuaa. In this scenario, the authentication is handled by XSUAA through the application router.

    RESULT

    • Click the My Node.js Application link. The browser window displays Application user: <e-mail>, showing the email you have used for your Cloud Foundry login.

    • Check that the myapp application is not accessible without authentication. To do that, refresh its previously loaded URL in a web browser – you should get a response 401 Unauthorized.

    Which service provides the authentication for your application?

  • Step 4

    Authorization in the SAP BTP, Cloud Foundry environment is also provided by the XSUAA service. In the previous example, the @sap/approuter package was added to provide a central entry point for the business application and to enable authentication. Now to extend the example, authorization will be added through the implementation of a users REST service. Different authorization checks will be introduced for the GET and CREATE operations to demonstrate how authorization works. The authorization concept includes elements such as roles, scopes, and attributes provided in the security descriptor file xs-security.json. For more information, see: Application Security Descriptor Configuration Syntax

    1. To introduce application roles, open the xs-security.json in the node-tutorial folder, and add scopes and role templates as follows:

      JSON
      Copy
      {
          "xsappname": "myapp",
          "tenant-mode": "dedicated",
          "scopes": [
            {
              "name": "$XSAPPNAME.Display",
              "description": "Display Users"
            },
            {
              "name": "$XSAPPNAME.Update",
              "description": "Update Users"
            }
          ],
          "role-templates": [
            {
              "name": "Viewer",
              "description": "View Users",
              "scope-references": [
                "$XSAPPNAME.Display"
              ]
            },
            {
              "name": "Manager",
              "description": "Maintain Users",
              "scope-references": [
                "$XSAPPNAME.Display",
                "$XSAPPNAME.Update"
              ]
            }
          ],
          "oauth2-configuration": {
              "redirect-uris": [
                  "https://*.cfapps.eu20.hana.ondemand.com/**"
          ]
        }
      }
      

      Two roles (Viewer and Manager) are introduced. These roles represent sets of OAuth 2.0 scopes or actions. The scopes are used later in the microservice’s code for authorization checks.

    2. Update the XSUAA service. To do that, in the node-tutorial directory run:

      Bash/Shell
      Copy
      cf update-service nodeuaa -c xs-security.json
      
    3. In the myapp folder, create a file users.json with the following content:

      JSON
      Copy
      [{
          "id": 0,
          "name": "John"
        },
        {
          "id": 1,
          "name": "Paula"
      }]
      

      This will be the initial list of users for the REST service.

    4. You need to add a dependency to body-parser that will be used for JSON parsing. To do that, in the myapp directory run:

      Bash/Shell
      Copy
      npm install body-parser --save
      
    5. Change the start.js file, adding GET and POST operations for the users REST endpoint. You can replace the initial code with the following one:

      JavaScript
      Copy
      const express = require('express');
      const passport = require('passport');
      const bodyParser = require('body-parser');
      const xsenv = require('@sap/xsenv');
      const { createSecurityContext, requests, constants, TokenInfo, JWTStrategy } = require("@sap/xssec").v3;
      
      const users = require('./users.json');
      const app = express();
      
      const services = xsenv.getServices({ uaa: 'nodeuaa' });
      
      passport.use(new JWTStrategy(services.uaa));
      
      app.use(bodyParser.json());
      app.use(passport.initialize());
      app.use(passport.authenticate('JWT', { session: false }));
      
      app.get('/users', function (req, res) {
        var isAuthorized = req.authInfo.checkLocalScope('Display');
        if (isAuthorized) {
          res.status(200).json(users);
        } else {
          res.status(403).send('Forbidden');
        }
      });
      
      app.post('/users', function (req, res) {
        const isAuthorized = req.authInfo.checkLocalScope('Update');
        if (!isAuthorized) {
          res.status(403).json('Forbidden');
          return;
        }
      
        var newUser = req.body;
        newUser.id = users.length;
        users.push(newUser);
      
        res.status(201).json(newUser);
      });
      
      const port = process.env.PORT || 3000;
      app.listen(port, function () {
        console.log('myapp listening on port ' + port);
      });
      

      IMPORTANT: Authorization checks are enforced by the xssec package in the @sap directory. To every request object using passport and xssec.JWTStrategy, a security context is attached as an authInfo object. The resulting request object is initialized with the incoming JWT token. To check the full list of methods and properties of the security context, see: Authentication for Node.js Applications

      As defined in the start.js file, for HTTP GET requests users need the Display scope to be authorized. For HTTP POST requests, they need to have the Update scope assigned.

    6. Update the UI to be able to send POST requests. To do that, go to web>resources and in the index.html file, replace the content with the following code:

      HTML
      Copy
      <html>
      <head>
        <title>JavaScript Tutorial</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
        <script>
          function fetchCsrfToken(callback) {
            jQuery.ajax({
                url: '/myapp/users',
                type: 'HEAD',
                headers: { 'x-csrf-token': 'fetch' }
              })
              .done(function(message, text, jqXHR) {
                callback(jqXHR.getResponseHeader('x-csrf-token'))
              })
              .fail(function(jqXHR, textStatus, errorThrown) {
                alert('Error fetching CSRF token: ' + jqXHR.status + ' ' + errorThrown);
              });
          }
      
          function addNewUser(token) {
            var name = jQuery('#name').val() || '--';
            jQuery.ajax({
                url: '/myapp/users',
                type: 'POST',
                headers: { 'x-csrf-token': token },
                contentType: 'application/json',
                data: JSON.stringify({ name: name })
              })
              .done(function() {
                alert( 'success' );
                window.location = '/myapp/users'
              })
              .fail(function(jqXHR, textStatus, errorThrown) {
                alert('Error adding new user: ' + jqXHR.status + ' ' + errorThrown);
              });
          }
      
          function addUser() {
            fetchCsrfToken(addNewUser);
          }
        </script>
      </head>
      <body>
        <h1>My Node.js Tutorial</h1>
        <a href="/myapp/users">Show users</a>
        <br/>
        <br/>
        <input type="text" id="name" placeholder="Type user name"></input>
        <input type="button" value="Add User" onClick="javascript: addUser()"></input>
      </body>
      </html>
      

      The UI contains a link to get all users, an input box to enter a user name, and a button to send “Create a new user” requests.

      In the sample, the code seems more complicated than expected. Clicking the Add User button tries to fetch a CSRF token, and on SUCCESS it sends a POST request with the users’ data as a JSON body. As you are using the application router, you need to get a CSRF token before sending the POST request. This token is required for all requests that change the state.

    7. Go to the node-tutorial directory and run:

      Bash/Shell
      Copy
      cf push
      

      This command will update both applications (myapp and web).

    8. Try to access myapp again (in a browser) in both ways – directly and through the web application router.

    RESULT

    • When you access it directly, you should still get a response 401 Unauthorized. This is a correct and expected behavior.
    • When you access the web application and click the Show users link, it should result in a 403 Forbidden response due to missing permissions. The same error is thrown if you try to add a new user.

    To get permissions, you need to create a role collection containing the roles Viewer and Manager and assign these roles to your user. You can do this only from the SAP BTP cockpit.

  • Step 5
    1. Open the SAP BTP cockpit and go to your subaccount.

    2. From the left-side menu, navigate to Security > Role Collections.

    3. Create a new role collection. For example, MyNodeAppRC.

    4. Click this role collection and then choose Edit.

    5. In the Roles tab, click the Role Name field.

    6. Type Viewer. From the displayed results, select the Viewer role that corresponds to your application, and choose Add.

    7. Repeat the same steps for Manager.

    8. Now go to the Users tab, and in the ID field, enter your e-mail. Then enter the same e-mail in the E-Mail field.

    9. Save your changes.

      Your role collection is now assigned to your user and contains the roles you need to view and manage the content of your application.

      Now you need to apply these changes to the myapp application by redeploying it again.

    10. Go back to the command line, and in the node-tutorial directory run:

      Bash/Shell
      Copy
      cf push
      

    RESULT

    Accessing the myapp application results in the following:

    • If you try to access it directly, a response 401 Unauthorized is still displayed due to lack of permissions (roles). This is a correct and expected behavior.

    • If you try to access it through the web application router, the Show users link will show the list of users - John and Paula. If you enter a new name, it will be successfully recorded in the user database.

    Tip: For the new result to take effect immediately, you might need to clear the cache of your browser. Or just open the web application URL in a private/incognito browser tab.


    Which of the following statements are correct?

Back to top