Search

Authorization and Access Control

This guide explains how to restrict access to data by adding respective declarations to CDS models, which are then enforced in service implementations.

Authorization means restricting access to data by adding respective declarations to CDS models, which are then enforced in service implementations. By adding such declarations, we essentially revoke all default access and then grant individual privileges.

Content

Authentication as Prerequisite

In essence, authentication is verifying the user’s identity and the presented claims such as granted roles and tenant membership. Briefly, authentication reveals who uses the service. In contrast, authorization controls how the user can interact with the application’s resources according to granted privileges. As the access control needs to rely on verified claims, authentication is a prerequisite to authorization.

From perspective of CAP, the authentication method is freely configurable and typically uses central identity and authentication services of the underlying platform. For the local development scenario, CAP brings a built-in authentication on basis of mock users. Find instructions how to set up authentication in these runtime-specific guides:

Static User Claims

CDS authorization is model-driven. This basically means it binds access rules for CDS model elements to user claims. For instance, access to a service or entity is dependent from the role a user has been assigned to. Or you can even restrict access on instance level, for example, to the user who has created the instance.
The generic CDS authorization is build on a CAP user concept, which is an _abstraction of a concrete user type determined by the platform’s identity service. This design decision makes different authentication strategies pluggable to generic CDS authorization.
After successful authentication, a (CAP) user is represented by the following properties:

  • Unique (logon) name identifying the user. Unnamed users have a fixed name such as system or anonymous.
  • Tenant in case of multitenant applications.
  • Roles the user has been granted by an administrator (see User Roles) or which are derived by the authentication level (see Pseudo Roles).
  • Attributes the user has been assigned by an administrator.

In the CDS model, some of the user properties can be referenced with $user prefix:

User Property Reference
Name $user
Tenant $user.tenant
Attribute (name <attribute>) $user.<attribute>

A single user attribute can have several different values. For instance, attribute $user.language could contain ['DE','FR'].

User Roles

As basis for access control, you can design conceptual roles that are application-specific. Such a role should reflect how a user can interact with the application. For instance, the role Vendor could describe users who are allowed to read sales articles and update sales figures. In contrast, a ProcurementManager can have full access to sales articles. Users can have several roles, which are assigned by an administrative user in the platform’s authorization management solution.

CDS-based authorization deliberately refrains from using technical concepts such as scopes as in OAuth in favor of user roles, which are closer to the conceptual domain of business applications. This also results in much smaller JWT tokens.

Pseudo Roles

Frequently, it’s required to define access rules that aren’t based on an application-specific user role, but rather on the authentication level of the request. For instance, a service could be accessible not only for identified, but also for anonymous (for example, unauthenticated) users. Such roles are called pseudo roles as they aren’t assigned by user administration, but are added at runtime automatically.

The following predefined pseudo roles are currently supported by CAP:

  • authenticated-user refers to (named or unnamed) users who have presented a valid authentication claim such as a logon token.
  • system-user denotes an unnamed user used for technical communication.
  • any refers to all users including anonymous ones (that means, public access without authentication).

The pseudo role system-user allows you to separate internal access by technical users from external access by business users. The technical user can come from a SaaS or the PaaS tenant. Such technical user requests typically run in a privileged mode without any restrictions on instance level. For example, an action that implements a data replication into another system needs to access all entities of subscribed SaaS tenants and can not be exposed to any business user. Note that system-user also implies authenticated-user.

In case of XSUAA authentication, the request user is attached with pseudo role system-user if the presented JWT token has been issued with grant type client_credentials or client_x509 for a trusted client application.

Restrictions

By default, CDS services have no access control. Hence, depending on the configured authentication, CDS services are initially open for anonymous users. To protect resources according to your business needs, you can define restrictions that make the runtime enforce proper access control. Alternatively, you may add custom authorization logic by means of authorization enforcement API.

Restrictions can be defined on different CDS resources:

  • Services
  • Entities
  • (Un)bound actions and functions

You can influence the scope of a restriction by choosing an adequate hierarchy level in the CDS model. For instance, a restriction on service level applies to all entities in the service. Additional restrictions on entities or actions can further limit authorized requests. See section combined restrictions for more details.

Beside the scope, restrictions can limit access to resources with regards to different dimensions:

Restricting Roles with @requires

You can use the @requires annotation to control which (pseudo-)role a user requires to access a resource:

annotate BrowseBooksService with @(requires: 'authenticated-user');
annotate ShopService.Books with @(requires: ['Vendor', 'ProcurementManager']);
annotate ShopService.ReplicationAction with @(requires: 'system-user');

In the example, service BrowseBooksService is open for authenticated, but not for anonymous users. A user who has role Vendor or ProcurementManager is allowed to access entity ShopService.Books. Unbound action ShopService.ReplicationAction can only be triggered by a technical user.

Restricting Events with @readonly and @insertonly

Annotate entities with @readonly or @insertonly to statically restrict allowed operations for all users as demonstrated in the example:

service BookshopService {
  @readonly entity Books {...}
  @insertonly entity Orders {...}
}

In addition, annotation @Capabilities from standard OData vocabulary is enforced similarly:

service SomeService {
  @Capabilities: { 
    InsertRestrictions.Insertable: true,
    UpdateRestrictions.Updatable: true,
    DeleteRestrictions.Deletable: false  
  }
  entity Foo { key ID : UUID }
}

Learn more about OData Annotations.

Finally, all direct write access requests to entities annotated with @cds.autoexposed are automatically rejected.

Learn more about Auto-exposed Entities.

Control Access with @restrict

You can use the @restrict annotation to define authorizations on a fine-grained level. In essence, all kinds of restrictions that are based on static user roles, the request operation, and instance filters can be expressed by this annotation.
The building block of such as restriction is a single privilege, which has the general form:

{ grant:<events>, to:<roles>, where:<filter-condition> }

whereas the properties are:

  • grant: one or more events the privilege applies to
  • to: one or more user roles the privilege applies to (optional)
  • where: a filter condition that further restricts access on instance level (optional).

Following values are supported:

  • grant accepts all standard CDS events (such as READ, CREATE, UPDATE, and DELETE) as well as action and function names. WRITE is a virtual event for all standard CDS events with write semantic (CREATE, DELETE, UPDATE, UPSERT) and * is a wildcard for all events.

  • The to property lists all user roles or pseudo roles the privilege applies to. Note that pseudo-role any does apply for all users and is the default if no value is provided.

  • The where-clause can contain a boolean expression in CQL-syntax that filters the instances the event applies to. As it allows user values (name, attributes etc.) and entity data as input, it’s suitable for dynamic authorizations based on the business domain. Supported expressions and typical use cases are presented in instance-based authorization.

A privilege is met, if and only if all properties are fulfilled for the current request. In the following example, only orders can be read by an Auditor who meets AuditBy element of the instance:

entity Orders @(restrict: [
    { grant: 'READ', to: 'Auditor', where: 'AuditBy = $user' }
  ]) {/*...*/}

If a privilege contains several events, only one of them needs to match the request event to comply with the privilege. The same holds, if there are multiple roles defined in the to property:

service ReviewService @(restrict: [ 
    { grant:['READ', 'WRITE'], to: ['Reviewer', 'Customer'] } 
  ]) {/*...*/}

In this example, all users that have role Reviewer or Customer can read or write on ReviewService.

You can build restrictions based on multiple privileges:

entity Orders @(restrict: [
    { grant: ['READ','WRITE'], to: 'Admin' },
    { grant: 'READ', where: 'buyer = $user' }
  ]) {/*...*/}

A request passes such a restriction if at least one of the privileges is met. In this example, Admin users can read and write entity Orders. But also a user can read all orders, which have a buyer property that matches the request user.

Similarly, the filter conditions of matched privileges are combined with logical OR:

entity Orders @(restrict: [
    { grant: 'READ', to: 'Auditor', where: 'country = $user.country },
    { grant: ['READ','WRITE'], where: 'CreatedBy = $user' },
  ]) {/*...*/}

Here an Auditor user can read all orders with matching country or which has been created by him- or herself.

Annotations such as @requires or @readonly are just convenience shortcuts for @restrict, for example:

  • @requires: 'Viewer' is equivalent to @restrict: [{grant:'*', to: 'Viewer'}]
  • @readonly is the same as @restrict: [{ grant:'READ' }]

Currently, the security annotations are only evaluated on the target entity of the request. Restrictions on associated entities touched by the operation aren’t regarded. This has the following implications:

  • Restrictions of (recursively) expanded or inlined entities of a READ request aren’t checked.
  • Deep inserts and updates are checked on the root entity only.

Find solution sketches how to deal with that.

Supported Combinations with CDS Resources

Restrictions can be defined on different types of CDS resources, but there are some limitations with regards to supported privileges:

CDS Resource grant to where Remark
service n/a n/a = @requires
entity  
action/function n/a n/a1 = @requires

1 Node.js supports static expressions that don’t have any reference to the model such as where: $user.level = 2.

Unsupported privilege properties are ignored by the runtime. Especially, in case of bound or unbound actions, the grant property is implicitly removed (assuming grant: '*' instead). The same also holds for functions:

service CatalogService {
  entity Products as projection on db.Products { ... }
  actions {
    @(requires: 'Admin')
    action addRating (stars: Integer);
  }    
  function getViewsCount @(restrict: [{ to: 'Admin' }]) () returns Integer;
}

Combined Restrictions

Restrictions can be defined on different levels in the CDS model hierarchy. Bound actions and functions refer to an entity, which in turn refers to a service. Unbound actions and functions directly refer to a service. As a general rule, all authorization checks of the hierarchy need to be passed (logical AND). This is illustrated in the following example:

service CustomerService @(requires: 'authenticated-user') {
  entity Products @(restrict: [ 
    { grant: 'READ' },
    { grant: 'WRITE', to: 'Vendor' },
    { grant: 'addRating', to: 'Customer'}
  ]) {/*...*/}
  actions {
     action addRating (stars: Integer);
  }    
  entity Orders @(restrict: [
    { grant: '*', to: 'Customer', where: 'CreatedBy = $user' }
  ]) {/*...*/}
  action monthlyBalance @(requires: 'Vendor') ();
}

The privilege for action addRating is defined on entity level.

The resulting authorizations are illustrated in the following access matrix:

Operation Vendor Customer authenticated-user anonymous
CustomerService.Products (READ) x
CustomerService.Products (WRITE) x x x
CustomerService.Products.addRating x x x
CustomerService.Orders (*) x 1 x x
CustomerService.monthlyBalance x x x

1 A Vendor user can only access the instances created by him- or herself.

The example models access rules for different roles in the same service. In general, this is not recommended due to the high complexity. Find best practices how to avoid this.

Restrictions and Draft Mode

Basically, the access control for entities in draft mode differs from the general restriction rules that apply to (active) entities. A user, who has created a draft, should also be able to edit (UPDATE) or cancel the draft (DELETE). The following rules apply:

  • If a user has the privilege to create an entity (CREATE), he or she also has the privilege to create a new draft entity and update, delete, and activate it.
  • If a user has the privilege to update an entity (UPDATE), he or she also has the privilege to put it into draft mode and update, delete, and activate it.
  • Draft entities can only be edited by the creator user.

As a result of the derived authorization rules for draft entities, you don’t need to take care of draft events when designing the CDS authorization model.

Inheritance of Restrictions

Service entities inherit the restriction from the database entity, on which they define a projection. An explicit restriction defined on a service entity replaces inherited restrictions from the underlying entity.

Entity Books on database level:

namespace db { 
  entity Books @(restrict: [
    { grant: 'READ', to: 'Buyer' },
  ]) {/*...*/}
}

Services BuyerService and AdminService on service level:

service BuyerService @(requires: 'authenticated-user'){
  entity Books as projection on db.Books; /* inherits */
}

service AdminService @(requires: 'authenticated-user'){
  entity Books @(restrict: [
    { grant: '*', to: 'Admin'} /* overrides */
  ]) as projection on db.Books;
}
Events Buyer Admin authenticated-user
BuyerService.Books (READ) x x
AdminService.Books (*) x x

We recommend to define restrictions on database entity level only in exceptional cases. Inheritance and override mechanism might lead to an unclear situation.

Warning
A service level entity can’t inherit a restriction with a where condition that doesn’t match the projected entity. The restriction has to be overridden in this case.

Instance-Based Authorization

The restrict annotation for an entity allows to enforce authorization checks that statically depend on the event type and user roles. In addition, you can define a where-condition that further limits the set of accessible instances. This condition, which acts like a filter, establishes an instance-based authorization.
The condition defined in the where-clause typically associates domain data with static user claims. Basically, it either filters the result set in queries or accepts only write operations on instances that meet the condition. Hence, the condition applies following standard CDS events only1:

  • READ (as result filter)
  • UPDATE (as reject condition)
  • DELETE (as reject condition)

1 Node.js supports static expressions that don’t have any reference to the model such as where: $user.level = 2 for all events including action and functions.

For instance, a user is allowed to read or edit Orders (defined with managed aspect) he or she’s created:

annotate Orders with @(restrict: [ 
  { grant: ['READ', 'UPDATE', 'DELETE'], where: 'CreatedBy = $user' } ]);

Or a Vendor can only edit articles on stock (that means Articles.stock positive):

annotate Articles with @(restrict: [ 
  { grant: ['UPDATE'], to: 'Vendor',  where: 'stock > 0' } ]);

You can define where-conditions in restrictions based on CQL-where-clauses.
Supported features are:

  • Predicates with arithmetic operators.
  • Combining predicates to expressions with logical operators and and or.
  • Value references to constants, user attributes, and entity data (elements including paths)

User Attribute Values

To refer to attribute values from the user claim, prefix the attribute name with ‘$user.’ as outlined in static user claims. For instance, $user.country refers to the attribute with name country.

In general, $user.<attribute-name> contains a list of attribute values that are assigned to the user. Following rules apply:

  • A predicate in the where clause evaluates to true if one of the attribute values from the list matches the condition.
  • An empty (or not defined) list means that the user is fully restricted with regards to this attribute (that means the predicate evaluates to false).
  • The special value $unrestricted in the list signals unrestricted access (that means the predicate evaluates to true).

For example, the condition where: countryCode = $user.country will grant a user with attribute values country = ['DE', 'FR'] access to entity instances, which have countryCode = DE or countryCode = FR. A user with country = ['$unrestricted'] is authorized to access all instances, whereas country = [] (or country not defined at all) doesn’t allow to access any of the instances.

Association Paths

The where-condition in a restriction can also contain CQL path expressions that navigate to elements of associated entities:

service SalesOrderService @(requires: 'authenticated-user') {
  entity SalesOrders @(restrict: [
     { grant: 'READ', 
       where: 'product.productType = $user.productType' } ]) {
    product: Association to one Products;
  }
  entity Products {
    productType: String(32); /*...*/
  }
}

Paths on 1:n associations (Association to many) are only supported, if the condition selects at most one associated instance.

Be aware of increased execution time when modeling paths in the authorization check of frequently requested entities. Working with materialized views might be an option for performance improvement in this case.

Warning
In Node.js association paths in where-clauses are currently only supported when using SAP HANA.

Best Practices

CAP authorization allows you to control access to your business data on a fine granular level. But please keep in mind that the high flexibility might end up in security vulnerabilities if not applied appropriately. In this perspective, lean and straightforward models are preferred. When modeling your access rules, the following recommendations can support you to design such models.

Choose Conceptual Roles

When defining user roles, one of the first ideas might be to align roles to the available operations on entities, which results in roles such as SalesOrders.Read, SalesOrders.Create, SalesOrders.Update, and SalesOrders.Delete etc. What is the problem with this approach? Think about the resulting number of roles the user administrator has to handle when assigning to business users. The administrator also would have to know the domain model precisely and understand the result of combining the roles. Similarly, assigning roles to operations only (Read, Create, Update, …) typically doesn’t fit your business needs.
We strongly recommend defining roles that describe how a business user interacts with the system. Roles like Vendor, Customer, or Accountant might be appropriate. With this approach, the application developers define the set of accessible resources in the CDS model for each role - and not the user administrator.

Prefer Single-Purposed, Use-Case Specific Services

Have a closer look at this example:

service CatalogService @(requires: 'authenticated-user') {
   entity Books @(restrict: [
    { grant: 'READ' },
    { grant: 'WRITE', to: 'Vendor', where: '$user.publishers = publisher' },
    { grant: 'WRITE', to: 'Admin' } ])
  as projection on db.Books;
  action doAccounting @(requires: ['Accountant', 'Admin']) ();
}

Four different roles (authenticated-user, Vendor, Accountant, Admin) share the same service CatalogService. As a result, it’s confusing how a user can use Books or doAccounting. Considering the complexity of this small example (4 roles, 1 service, 2 resources), this approach might introduce a security risk, especially in case the model is larger and subject to adaption. Moreover, UIs defined for this service will likely appear unclear as well.
The fundamental purpose of services is to expose business data in a specific way. Hence, the more straightforward way is to spend a service for each of the roles:

@path:'browse'
service CatalogService @(requires: 'authenticated-user') {
  @readonly entity Books  
  as select from db.Books { title, publisher, price };
}

@path:'internal'
service VendorService @(requires: 'Vendor') {
  entity Books @(restrict: [
    { grant: 'READ' },
    { grant: 'WRITE', to: 'vendor', where: '$user.publishers = publisher' } ]) 
  as projection on db.Books;
}

@path:'internal'
service AccountantService @(requires: 'Accountant') {
  @readonly entity Books as projection on db.Books;
  action doAccounting();
}
/*...*/

You can tailor the exposed data according to the corresponding role, even on level of entity elements like done in CatalogService.Books.

Prefer Dedicated Actions for Specific Use-Cases

In some cases it might be helpful to restrict entity access as far as possible and create actions with dedicated restrictions for specific use cases like in the following example:

service GitHubRepositoryService @(requires: 'authenticated-user') {
  @readonly entity Organizations as projection on GitHub.Organizations actions {
    action rename @(requires: 'Admin') (newName : String);
    action delete @(requires: 'Admin') ();
  };
}

This service allows querying organizations for all authenticated users. In addition, Admin users are allowed to rename or delete. Granting UPDATE to Admin would allow administrators to change organization attributes that aren’t meant for adaption.

Think About Domain-Driven Authorization

In frequent cases, static roles don’t fit into an intuitive authorization model. Instead of making authorization dependent from static properties of the user, it’s often more appropriate to derive access rules from the business domain. For instance, all users assigned to a department (in the domain) are allowed to access the data of the organization comprising the department. Relationship in the entity model (for example, a department assignment to organization) influence authorization rules at runtime. In contrast to static user roles, dynamic roles are fully domain-driven.

Advantages of dynamic roles are:

  • Most flexible way to define authorizations.
  • Induced authorizations according to business domain.
  • Application-specific authorization model and intuitive UIs.
  • Decentralized role management for application users (no central user administrator required).

Drawbacks to be considered are:

  • Additional effort for modeling and designing application-specific role management (entities, services, UI).
  • Potentially higher security risk due to less usage of framework functionality.
  • Sharing authorization management with other (none-CAP) application is harder to achieve.
  • Dynamic role enforcement can introduce a performance penalty.

Control Exposing of Associations and Compositions

Note that exposed associations (and compositions) might disclose unauthorized data. Consider the following scenario:

namespace db {
  entity Employees : cuid { // autoexposed!
    name: String(128);
    team: Association to Teams;
    contract: Composition to one Contracts;
  }  
  entity Contracts @(requires:'Manager') : cuid { // autoexposed!
    salary: Decimal; 
  } 
  entity Teams : cuid {
    members: Composition of many Employees on members.team = $self;
  }
}

service ManageTeamsService @(requires:'Manager') {
  entity Teams as projection on db.Teams;
}

service BrowseEmployeesService @(requires:'Employee') {
  @readonly entity Teams as projection on db.Teams; // navigate to Contracts!
}

A team (entity Teams) contains members of type Employees. An employee refers to a single contract (entity Contracts) which contains sensitive information that should be visible only to Manager users. Employee users should be able to browse the teams and their members, but aren’t allowed to read or even edit their contracts.
As db.Employees and db.Contracts are auto-exposed, managers can navigate to all instances via service entity ManageTeamsService.Teams (for example, OData request /ManageTeamsService/Teams?$expand=members($expand=contract)).
It’s important to notice, that this also holds for an Employee user, as only the target entity BrowseEmployeesService.Teams has to pass the authorization check in the generic handler, and not the associated entities.

To solve this security issue, introduce a new service entity BrowseEmployeesService.Employees that removes the navigation to Contracts from the projection:

service BrowseEmployeesService @(requires:'Employee') {
  @readonly entity Employees
  as projection on db.Employees excluding { contracts }; // hide contracts!
  
  @readonly entity Teams as projection on db.Teams;
}

Now, an Employee user can’t expand the contracts as the composition isn’t reachable anymore from the service.

Associations without navigation links (for example, when an associated entity isn’t exposed) are still critical with regards to security.

Design Authorization Model from Start

As shown before, defining an adequate authorization strategy has a deep impact on the service model. Apart from the fundamental decision, if you want to build your authorization on dynamic roles, authorization requirements might result in rearranging service and entity definitions completely. In worst case, this means to rewrite huge parts of the application (including UI). Hence, it’s strongly recommended to take security design into consideration in early stage of your project.

Keep as Simple as Possible

  • If different authorizations are needed for different operations, it’s easier to have them defined at service level. If you start defining them at entity level, all possible operations must be specified otherwise the not mentioned ones are automatically forbidden.
  • If possible, try to define your authorizations either on service or on entity level. Mixing both variants increases complexity and not all combinations are supported either.

Separation of Concerns

Consider using CDS Aspects to separate the actual service definitions from authorization annotations as follows:

services.cds

service ReviewsService {
  /*...*/
} 

service CustomerService {
  entity Orders {/*...*/}
  entity Approval {/*...*/}
}

services-auth.cds

using { ReviewsService, CustomerService } from './services';

annotate ReviewsService with @(requires: 'identified-user');
annotate CustomerService with @(requires: 'authenticated-user');

annotate CustomerService.Orders with @(restrict: [
  { grant: ['READ','WRITE'], to: 'admin' },
  { grant: 'READ', where: 'buyer = $user' },
]);

annotate CustomerService.Approval with @(restrict: [
  { grant: 'WRITE', where: '$user.level > 2' }
]);

This keeps your actual service definitions concise and focused on structure only. It also allows giving authorization models a separate ownership and lifecycle.

Programmatic Enforcement

The service provider frameworks automatically enforce restrictions in generic handlers. They evaluate the annotations in the CDS models and for example:

  • Reject incoming requests if static restrictions aren’t met.
  • Add corresponding filters to queries for instance-based authorization etc.

In case generic enforcement doesn’t fit your needs, you can override or adapt it with programmatic enforcement in custom handlers.

Programmatic Enforcement in Node.js

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 following sample would use this API to programmatically enforce the previously defined restrictions:

const cds = require('@sap/cds')

cds.serve ('ReviewsService') .with ((srv)=>{
  srv.before ('*', req =>
    req.user.is('identified') || req.reject(403)
  )
})

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)
  )
})

Programmatic Enforcement in Java

Use the Java APIs to programmatically enforce restrictions in your custom request handlers. See Enforcement API & Custom Handlers for more details.

Role Assignments with XSUAA

Information about roles and attributes has to be made available to the UAA platform service. This information enables the respective JWT tokens to be constructed and sent with the requests for authenticated users. In particular, the following happens automatically behind-the-scenes upon build:

1. Roles and Attributes Are Filled into XSUAA Configuration

Scopes, attributes, and role templates are derived out of the CDS model. This results in:

cds-security.json

{
  "xsappname": "bookshop", "tenant-mode": "dedicated",
  "scopes": [
    { "name": "$XSAPPNAME.admin", "description": "admin" }
  ],
  "attributes": [
    { "name": "level", "description": "level", "valueType": "s" }
  ],
  "role-templates": [
    { "name": "admin", "scope-references": [ "$XSAPPNAME.admin" ], "description": "generated" }
  ]
}

You can have such a file generated through
cds compile service.cds --to xsuaa > cds-security.json.

For every role name in the CDS model, one scope and one role template are generated having exactly the name of the CDS role name. The modeled role and scope names in the CDS files might contain invalid characters from XSUAA perspective. Please refer to the SAP Help documentation for the syntax of the xs-security.json. You can also find hints there how to complete this file manually for the complete setup of your XSUAA instance besides the authorization aspect. If you create the xs-security.json manually or you already have an existing file, make sure that the scope names in the file exactly match the role names in the CDS Model as these scope names will be checked at runtime.

2. XSUAA Configuration Is Completed and Published

Depending on whether MTA deployment is used, choose one approach:

Through MTA Build

This merges any inline configuration from mta.yaml (see the config block) and the cds-security.json file:

mta.yml

resources:
  name: my-uaa
  type: org.cloudfoundry.managed-service
  parameters:
    service: xsuaa
    service-plan: application
    path: ./cds-security.json  # include cds managed scopes and role templates
    config:
      xsappname: my-uaa-${space}
      tenant-mode: dedicated   # use 'shared' for multi-tenant deployments
      scopes: []   # more scopes

If there are conflicts, the MTA security configuration has priority.

Deployment of such an MTA uploads the XSUAA configuration to SAP Cloud Platform.

Learn more about building and deploying MTA applications.

Manual

Invoke cds manually, which merges a custom ‘base’ file with the derived configuration from the model:

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

Use cf create-service xsuaa application <servicename> -c xs-security.json to create the XSUAA service with the XSUAA configuration, or cf update-service <servicename> -c xs-security.json to update the configuration.

3. Assembling Roles and Assigning Roles to Users

This is a manual step an administrator would do in SAP Cloud Platform Cockpit. See here for more details. If a user attribute isn’t set for a user in the IDP of the SAP Cloud Platform Cockpit, this means that the user has no restriction for this attribute. For example, if a user has no value set for an attribute “Country”, they’re allowed to see data records for all countries. In the xs-security.json, the entity attribute has a property valueRequired where the developer can specify whether an unrestricted access is possible by not assigning a value to the attribute.

4. Scopes Are Narrowed to Local Roles

Based on this, the JWT token for an administrator contains a scope my.app.admin. From within service implementations of my.app you can reference the scope:

req.user.is ("admin")

… and, if necessary, from others by:

req.user.is ("my.app.admin")


See the following sections for more details:

Show/Hide Beta Features