SuperAPI AI Directory

security-auth

Security & auditing

View repository

Updated 10-16 09:47

About this skill

Security & Authorization — CAP Best Practices

Primary reference: https://cap.cloud.sap/docs/guides/security/overview Authorization: https://cap.cloud.sap/docs/guides/authorization

Service-level access control

// Restrict entire service to authenticated users
@requires: 'authenticated-user'
service CatalogService { ... }

// Multiple roles (OR logic)
@requires: ['admin', 'manager']
service AdminService { ... }

Entity-level @restrict

service OrderService @(requires: 'authenticated-user') {

  entity Orders @(restrict: [
    { grant: 'READ',   to: ['viewer', 'editor', 'admin'] },
    { grant: 'CREATE', to: ['editor', 'admin'] },
    { grant: 'UPDATE', to: ['editor', 'admin'], where: 'createdBy = $user' },
    { grant: 'DELETE', to: 'admin' },
  ]) as projection on db.Orders;

}

Instance-based authorization (row-level security)

❌ Wrong vs ✅ Correct syntax

// ❌ WRONG — operations instead of grant, roles instead of to, where as array
entity Orders @(restrict: [
  { operations: ['READ'], roles: ['authenticated-user'] },
  { operations: ['UPDATE'], where: ['owner = $user.id'] }
])

// ✅ CORRECT
entity Orders @(restrict: [
  { grant: 'READ',   to: 'authenticated-user' },
  { grant: 'UPDATE', to: 'editor', where: 'createdBy = $user' }
])
// User can only update their own records
{ grant: 'UPDATE', to: 'editor', where: 'createdBy = $user' }

// Filter by user attribute (e.g. cost center)
{ grant: 'READ', to: 'employee', where: 'costCenter = $user.attr.costCenter' }

Expose user attributes from XSUAA/IAS in xs-security.json:

{
  "role-templates": [{
    "name": "Employee",
    "attribute-references": ["costCenter"]
  }]
}

Checking authorization in handlers

async init() {
  this.before('UPDATE', Orders, this.checkOwner)
}

async checkOwner(req) {
  const { user } = req
  if (user.is('admin')) return  // admins bypass

  const order = await SELECT.one(Orders, req.params[0]).columns('createdBy')
  if (!order) req.reject(404)
  if (order.createdBy !== user.id) req.reject(403, 'You can only edit your own orders')
}

Programmatic role checks

const { user } = req
user.is('admin')            // role check (boolean)
user.id                     // 'alice@example.com'
user.tenant                 // tenant ID (multitenancy)
user.attr.costCenter        // custom attribute from XSUAA
user.locale                 // 'en', 'de', etc.

xs-security.json — XSUAA scopes and roles

{
  "xsappname": "my-cap-app",
  "tenant-mode": "dedicated",
  "scopes": [
    { "name": "$XSAPPNAME.admin",  "description": "Administrative access" },
    { "name": "$XSAPPNAME.editor", "description": "Create and edit records" },
    { "name": "$XSAPPNAME.viewer", "description": "Read-only access" }
  ],
  "role-templates": [
    {
      "name": "Admin",
      "scope-references": ["$XSAPPNAME.admin", "$XSAPPNAME.editor", "$XSAPPNAME.viewer"]
    },
    {
      "name": "Editor",
      "scope-references": ["$XSAPPNAME.editor", "$XSAPPNAME.viewer"]
    },
    {
      "name": "Viewer",
      "scope-references": ["$XSAPPNAME.viewer"]
    }
  ]
}

Local development mock users

In .cdsrc.json or package.json:

{
  "cds": {
    "requires": {
      "auth": {
        "kind": "mocked",
        "users": {
          "alice": { "roles": ["admin"] },
          "bob":   { "roles": ["editor"] },
          "carol": { "roles": ["viewer"], "attr": { "costCenter": "CC100" } }
        }
      }
    }
  }
}

Test with: http://localhost:4004 --user alice

IAS (Identity Authentication Service) setup

{
  "cds": {
    "requires": {
      "auth": "ias"
    }
  }
}

Bind the identity service in mta.yaml:

- name: my-app-srv
  requires:
    - name: my-ias-instance

Common mistakes to avoid

  • ❌ Using @requires: 'any' when you mean 'authenticated-user' (they differ!)
  • ❌ Relying only on client-side checks — always enforce in the service layer
  • ❌ Hardcoding user IDs in where clauses instead of using $user
  • ❌ Forgetting to add scopes to xs-security.json — roles without scopes don't work
  • ❌ Not testing with different mock users locally before deploying
  • ❌ Using @restrict without @requires on the service — annotations don't cascade automatically