Search

    Authentication

    This guide is about authenticating users on incoming HTTP requests.

    Configuration and Defaults

    CDS ships with a few prebuilt authentication strategies, which are switched on by default depending on your environment.

    Defaults

    The following default configurations apply based on the NODE_ENV:

    Configuration

    You can override the defaults by providing specific configuration to the cds.requires.auth configuration option in the cds section of your package.json or in .cdsrc.json. For example:

    "requires": {
      "auth": {
        "strategy": <String>,  //> to configure a pre-built one
        "impl": <String>,      //> for custom-defined one
      }
    }
    

    In addition you may add more properties depending on the chosen implementation, as documented in the following sections.

    Introspection

    Run cds env get requires.auth in your project root to find out the effectively configured authentication strategy for your current environment (that means, development vs production).

    Dummy Authentication

    This strategy creates a user that passes all authorization checks. It’s meant for temporarily disabling the @requires and @restrict annotations at development time.

    Configuration

    In .cdsrc.json or cds section of package.json:

    "requires": {
      "auth": {
        "strategy": "dummy"
      }
    }
    

    Mocked Authentication

    This is the authentication strategy uses passport with basic authentication to use mock users during development.

    This mode must only be used during development. It’s disabled if the NODE_ENV environment variable is set to production.

    When testing different users in the browser, it’s best to use an incognito window, because logon information might otherwise be reused.

    Prerequisites

    You need to add passport to your project:

    npm add passport
    

    Default Configuration

    The default configuration shipped with @sap/cds looks like that:

    cds.requires.auth = {
      strategy: "mock",
      users: {
        "alice": { "roles": ["admin"] },
        "bob": { "roles": ["builder"] },
        "*": true //> all other logins are allowed as well
      }
    }
    

    Configuring Specific Users

    You can override the users property in the cds section of your package.json or in .cdsrc.json as follows:

    "cds": { // in case of package.json
      "requires": {
        "auth": {
          "strategy": "mock",
          "users": {
            "<user.id>": { 
              "password": "<password>", 
              "roles": ["<role-name>", ...],
              "userAttributes": { ... }
            },
          }
        }
      }
    }
    

    Learn more about Configuration in CAP Node.js.

    Token-Based Authentication (JWT)

    This is the strategy to be used in production. User identity, as well as assigned roles and user attributes, are provided at runtime, by a bound instance of the ‘user account and authentication’ service (UAA). This is done in form of a JWT token in the Authorization header of incoming HTTP requests.

    The following steps assume you’ve set up the Cloud Foundry Command Line Interface.

    1. Log in to Cloud Foundry:

      cf l -a <api-endpoint>
      

      If you don’t know the API endpoint, have a look at section Regions and API Endpoints Available for the Cloud Foundry Environment.

    2. Go to the project you have created in Getting started in a Nutshell.

    3. Start the watch process on your project:

      cds watch
      
    4. Enable the strategy by installing the @sap/xssec and @sap/xsenv package:

      npm install @sap/xssec @sap/xsenv
      

    Next, you need to bind and configure the UAA service.

    Create XSUAA Configuration

    1. Create a folder called gen and compile your CDS model with authentication annotations into a full xs-security.json:

      mkdir gen
      cds compile srv/ --to xsuaa > gen/xs-security.json
      

      Note how in xs-security.json the admin scope from the CDS model has materialized as a scope and a role template.

    2. Create an XSUAA service with this configuration:

      cf create-service xsuaa application bookshop-uaa -c gen/xs-security.json
      

      Later on, if you’ve changed the scopes, you can use cf update-service bookshop-uaa -c xs-security.json to update the configuration.

      Troubleshooting: Invalid JSON

    This step is necessary for locally running apps and for apps deployed on Cloud Foundry.

    If you’re using SAP Business Application Studio as your development environment, you need to add another property to the xs-security.json to configure which redirect URIs are allowed.

    The format is the following:

    "oauth2-configuration": {
        "redirect-uris": [
        "https://[<dev-space-identifier>].[<landscape>].applicationstudio.cloud.sap/"
      ]
    }
    

    There are three options for the configuration:

    • Use the full URL of your application, which is the most secure option. This is a specific configuration for your dev space and should not be submitted or shared.
      • Get the URL using Ports:Preview from the command palette.
    • Use a partial URL containing the landscape and the host (for example: https://trial.applicationstudio.cloud.sap/) or only the host (for example: https://applicationstudio.cloud.sap/).

    See section Application Security Descriptor Configuration Syntax for more details on configuration options.

    Configure the Application

    1. Create a default-env.json file in the root of your project and insert this code:

      {
        "VCAP_SERVICES": {
       "xsuaa": [
         {
           "name": "bookshop-uaa",
           "label": "xsuaa",
           "tags": [ "xsuaa" ],
           "credentials": {
             [...]
           }
         }
       ]
        }
      }
      
    2. Create a service key:

      cf create-service-key bookshop-uaa bookshop-uaa-key
      cf service-key bookshop-uaa bookshop-uaa-key
      

      You do this, to gain access to the XSUAA credentials.

    3. Copy the JSON snippet from the console into the default-env.json file in the VCAP_SERVICES.xsuaa.credentials block.

      This step is only necessary if your application is running locally.

    4. Enhance your app’s configuration in package.json by a uaa section inside the cds.requires block:

      "cds": {
        "requires": {
       "uaa": {
         "kind": "xsuaa"
       }
        }
      }
      

      This configuration, together with the credentials from default-env.json, is used by the Node.js runtime to validate the JWT token. To verify it, you can run cds env list requires.uaa, which prints the full uaa configuration including the credentials.

    5. Install node modules that are required at runtime to authenticate the user and to read the JWT token:

      npm install --save passport @sap/xssec @sap/audit-logging
      

    Set Up the Roles for the Application

    Once you’ve deployed your application to the Cloud Foundry environment for SAP BTP and created the service binding to XSUAA, you enter the SAP BTP Cockpit. In the cockpit, you set up the roles and role collections and assign the role collections to your users. This brings the necessary authorization information into the JWT token when the user logs on to your application through XSUAA and approuter.

    Since XSUAA configuration in cloud cockpit only works on existing applications, you need to temporarily push the app and bind it to the previously created service. You don’t need the app to be running at this stage.

    1. Push the application without start:

      cf target  # make sure that you're logged in to the correct org and space
      cf push bookshop --no-start --no-manifest --random-route
      
    2. Bind your application to your xsuaa service instance:

      cf bind-service bookshop bookshop-uaa
      
    3. Open the SAP BTP Cockpit.

      For your trial account, this is: https://cockpit.hanatrial.ondemand.com

    4. Create a role collection.

      The roles collections are created on subaccount level in the cockpit. Navigate to your subaccount and then choose Security > Role Collections. Create role collections

    5. Create roles and add them to your role collection.

      The roles are created on application level in the cockpit and based on your role templates. Navigate to the application in the correct space in your subaccount and then choose Security > Roles. The wizard takes you through all needed steps. Remember to include your role to the role collection you created before. Create roles

    6. Assign the role collections to users.

      The user role assignment is done in the Trust Configuration of your subaccount. Select the identity provider where the user is authenticated. Assign role collection to user. Select IDP Enter the E-mail address and select Show Assignments, to see already existing assignments. Then select Assign Role Collection to add your user to the role collection. Assign role collection to user. Select user

      See Assign Role Collections in SAP BTP documentation for more details.

    Running Approuter

    The approuter component implements the necessary handshake with XSUAA to let the user log in interactively. The resulting JWT token is sent to the application where it’s used to enforce authorization.

    1. Create a file app/package.json with the following content:

      {
        "name": "approuter",
        "dependencies": {
       "@sap/approuter": "^8"
        },
        "scripts": {
       "start": "node node_modules/@sap/approuter/approuter.js"
        }
      }
      
    2. Create the approuter configuration file app/xs-app.json with the following content:

      {
        "routes": [{
       "source": "^/(.*)",
       "destination": "srv_api"
       }]
      }
      
    3. Create a file app/default-env.json with the following content:

      {
       "destinations" : [
         {
           "name": "srv_api",
           "url": "<service-url>",
           "forwardAuthToken": true
         }
       ]
      }
      

      where

      • srv_api is the destination name from xs-app.json.
      • <service-url> is the service root of your app, for example, http://localhost:4004
    4. Copy the VCAP_SERVICES block of file default-env.json into file app/default-env.json. This tells approuter which UAA instance to contact.

    5. In app/ folder, run:

      npm install  # install approuter modules
      npm start    # start approuter
      

      This starts an approuter instance on http://localhost:5000

      Since it only serves static files or delegates to the backend service, you can keep the server running. It doesn’t need to be restarted after you have changed files.

    6. After the approuter is started, log in at http://localhost:5000 and verify that the routes are protected as expected. In our example, if you assigned the admin scope to your user in SAP BTP cockpit, you can now access http://localhost:5000/admin.


    To test UIs w/o a running UAA service, just add this to xs-app.json: "authenticationMethod": "none"

    Token-Based Authentication with SAML Attributes on SAP BTP

    Authentication kind xsuaa is a logical extension of kind JWT that additionally offers access to SAML attributes through req.user.attr (for example, req.user.attr.familyName).

    It’s recommended to only use this authentication kind if it’s necessary for your use case, as it denotes a lock-in to SAP BTP.

    requires @sap/xssec@^3

    Custom-Defined Authentication

    In case the supported authentication strategies don’t fulfill the necessary requirements, developers can provide a custom express middleware for authentication.

    Essentially, custom authentication middlewares must fulfill the req.user contract by assigning an instance of cds.User or a look-alike to the incoming request at req.user.

    Configuration

    You can configure an own implementation in .cdsrc.json or the cds section of your package.json as follows:

    "requires": {
      "auth": {
        "impl": "srv/auth.js" // > relative path from project root
      }
    }
    

    req.user cds.User class

    Represents the currently logged-in user as filled into req.user by authentication middlewares. Simply create instances of cds.User or of subclasses thereof in custom middlewares. For example:

    const cds = require('@sap/cds')
    const DummyUser = new class extends cds.User { is:()=>true }
    module.exports = (req,res,next) => {
      req.user = new DummyUser('dummy')
      next()
    }
    

    Properties & Methods

    user.id : string

    A user’s unique ID. It corresponds to $user in @restrict annotations of your CDS models (Also in JavaScript, user can act as a shortcut for user.id in comparisons.)

    user.locale : string

    The user’s preferred locale. cds.Users implements that as a getter that will resolve to the normalized Accept-Language header of incoming requests.

    user.tenant : string

    The user’s tenant ID, or undefined if not run in multitenancy mode.

    user.attr.<> : string

    User-related attributes, for example, from JWT tokens These correspond to $user.<> in @restrict annotations of your CDS models

    user.is (<role>) boolean

    Checks if user has assigned the given role. Example usage:

    if (req.user.is('admin')) ...
    

    The role names correspond to the values of @requires and the @restrict.grants.to annotations in your CDS models.

    cds.User.Privileged class

    In some cases, you might need to bypass authorization checks while consuming a local service. For this, you can create a transaction with a privileged user as follows:

    this.before('*', async function (req) {
      // pass tenant if needed
      const user = new cds.User.Privileged({ tenant: 'myTenant' })
      const tx = this.transaction({ user })
      await tx.run(INSERT.into('RequestLog').entries({
        url: req._.req.url,
        user: req.user.id
      }))
      await tx.commit()
    })
    

    Authorization Enforcement

    Following are the Node.js APIs you can use for implementing your enforcement (swift has corresponding ones):

    API Description
    req.user Shortcut for req.user.id in queries
    req.user.id Access the current user’s unique ID, an arbitrary string
    req.user.is(<role>) 1 Check whether the user has assigned the given role
    req.user.attr.<attr> Access user-related attributes from JWT tokens
    req.reject(403, ...) Reject a request due to missing authorizations
    req.query.where(...) Add a filter to the query’s WHERE clause

    1 The function req.user.is(<role>) accepts role names introduced in the current application’s service models.

    For example, the authorization of the following CDS service:

    service CustomerService @(requires: 'authenticated-user'){
      entity Orders @(restrict: [ 
        { grant: ['READ','WRITE'], to: 'admin' },
        { grant: 'READ', where: 'buyer = $user' },
      ]){/*...*/}
      entity Approval @(restrict: [
        { grant: 'WRITE', where: '$user.level > 2' } 
      ]){/*...*/}
    }
    

    can be programmatically enforced by means of the API as follows:

    const cds = require('@sap/cds')
    
    cds.serve ('CustomerService') .with ((srv)=>{
      srv.before ('*', req =>
       req.user.is('authenticated') || req.reject(403)
      )
      srv.before (['READ', 'CREATE'], 'Orders', req =>
        req.user.is('admin') || req.reject(403)
      )
      srv.before ('READ', 'Orders', req =>
        req.query.where('buyer =',req.user)
      )
      srv.before ('*', 'Approval', req =>
        req.user.level > 2 || req.reject(403)
      )
    })
    
    Show/Hide Beta Features